Skip to content

Commit 369c26c

Browse files
FehintolaObafemitimayabi2020peombwazengincnotin
committed
Fehintolaobafemi/methodanduri (#2751)
* Making changes to how httpmethod and uri is processed --------- Co-authored-by: Tim <[email protected]> Co-authored-by: Peter Ombwa <[email protected]> Co-authored-by: Peter Ombwa <[email protected]> Co-authored-by: Mustafa Zengin <[email protected]> Co-authored-by: Clément Notin <[email protected]> Co-authored-by: Microsoft Graph DevX Tooling <[email protected]> Co-authored-by: Vincent Biret <[email protected]> Co-authored-by: Vincent Biret <[email protected]> Co-authored-by: Subhajit Ray (from Dev Box) <[email protected]>
1 parent 9ec61a5 commit 369c26c

File tree

10 files changed

+164
-45
lines changed

10 files changed

+164
-45
lines changed

src/Authentication/Authentication.Core/Common/GraphSession.cs

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public class GraphSession : IGraphSession
5656
/// </summary>
5757
public IGraphOption GraphOption { get; set; }
5858

59+
/// <summary>
60+
/// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts.
61+
/// </summary>
62+
public IGraphRequestProofofPossession GraphRequestProofofPossession { get; set; }
63+
5964
/// <summary>
6065
/// Represents a collection of Microsoft Graph PowerShell meta-info.
6166
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
using Azure.Core;
6+
using Azure.Identity;
7+
using System;
8+
using System.Net.Http;
9+
10+
namespace Microsoft.Graph.PowerShell.Authentication
11+
{
12+
public interface IGraphRequestProofofPossession
13+
{
14+
Uri Uri { get; set; }
15+
HttpMethod HttpMethod { get; set; }
16+
AccessToken AccessToken { get; set; }
17+
string ProofofPossessionNonce { get; set; }
18+
PopTokenRequestContext PopTokenContext { get; set; }
19+
Request Request { get; set; }
20+
InteractiveBrowserCredential BrowserCredential { get; set; }
21+
}
22+
}

src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ public interface IGraphSession
1212
IDataStore DataStore { get; set; }
1313
IRequestContext RequestContext { get; set; }
1414
IGraphOption GraphOption { get; set; }
15+
IGraphRequestProofofPossession GraphRequestProofofPossession { get; set; }
1516
}
1617
}

src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
<ItemGroup>
1414
<PackageReference Include="Azure.Identity" Version="1.12.0-beta.1" />
1515
<PackageReference Include="Azure.Core" Version="1.39.0" />
16-
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0" />
16+
<PackageReference Include="Azure.Core.Experimental" Version="0.1.0-preview.33" />
17+
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0-beta.1" />
1718
<PackageReference Include="Microsoft.Graph.Core" Version="3.1.10" />
1819
<PackageReference Include="Microsoft.Identity.Client" Version="4.60.3" />
1920
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.60.3" />

src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs

+71-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ------------------------------------------------------------------------------
22
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
33
// ------------------------------------------------------------------------------
4+
using Azure;
45
using Azure.Core;
56
using Azure.Core.Diagnostics;
67
using Azure.Core.Pipeline;
@@ -16,6 +17,7 @@
1617
using System.IO;
1718
using System.Linq;
1819
using System.Net.Http;
20+
using System.Net.Http.Headers;
1921
using System.Security.Claims;
2022
using System.Security.Cryptography.X509Certificates;
2123
using System.Text.RegularExpressions;
@@ -91,7 +93,7 @@ private static bool IsWamSupported()
9193
}
9294

9395
//Check to see if ATPoP is Supported
94-
private static bool IsATPoPSupported()
96+
public static bool IsATPoPSupported()
9597
{
9698
return GraphSession.Instance.GraphOption.EnableATPoPForMSGraph;
9799
}
@@ -131,10 +133,17 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
131133
interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext));
132134
interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext);
133135

136+
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
137+
if (IsATPoPSupported())
138+
{
139+
GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext = CreatePopTokenRequestContext(authContext);
140+
GraphSession.Instance.GraphRequestProofofPossession.BrowserCredential = interactiveBrowserCredential;
141+
}
142+
134143
if (!File.Exists(Constants.AuthRecordPath))
135144
{
136145
AuthenticationRecord authRecord;
137-
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
146+
//var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
138147
if (IsWamSupported())
139148
{
140149
// Adding a scenario to account for Access Token Proof of Possession
@@ -143,45 +152,9 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
143152
// Logic to implement ATPoP Authentication
144153
authRecord = await Task.Run(() =>
145154
{
146-
// Creating a Request to retrieve nonce value
147-
string popNonce = null;
148-
var popNonceToken = "nonce=\"";
149-
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)
150-
HttpClient httpClient = new(new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true });
151-
HttpResponseMessage response = httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, resourceUri)).Result;
152-
153-
// Find the WWW-Authenticate header in the response.
154-
var popChallenge = response.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
155-
var nonceStart = popChallenge.Parameter.IndexOf(popNonceToken) + popNonceToken.Length;
156-
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
157-
popNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);
158-
159-
// Refresh token logic --- start
160-
var popTokenAuthenticationPolicy = new PopTokenAuthenticationPolicy(interactiveBrowserCredential as ISupportsProofOfPossession, $"https://graph.microsoft.com/.default");
161-
var pipelineOptions = new HttpPipelineOptions(new PopClientOptions()
162-
{
163-
Diagnostics =
164-
{
165-
IsLoggingContentEnabled = true,
166-
LoggedHeaderNames = { "Authorization" }
167-
},
168-
});
169-
pipelineOptions.PerRetryPolicies.Add(popTokenAuthenticationPolicy);
170-
171-
var _pipeline = HttpPipelineBuilder.Build(pipelineOptions, new HttpPipelineTransportOptions { ServerCertificateCustomValidationCallback = (_) => true });
172-
173-
using var request = _pipeline.CreateRequest();
174-
request.Method = RequestMethod.Get;
175-
request.Uri.Reset(resourceUri);
176-
177-
// Manually invoke the authentication policy's process method
178-
popTokenAuthenticationPolicy.ProcessAsync(new HttpMessage(request, new ResponseClassifier()), ReadOnlyMemory<HttpPipelinePolicy>.Empty);
179-
// Refresh token logic --- end
180-
181155
// Run the thread in MTA.
182-
var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: popNonce, request: request);
183-
//var token = interactiveBrowserCredential.GetToken(popContext, cancellationToken);
184-
return interactiveBrowserCredential.Authenticate(popContext, cancellationToken);
156+
//GraphSession.Instance.GraphRequestProofofPossession.AccessToken = interactiveBrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken).Result;
157+
return interactiveBrowserCredential.AuthenticateAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken);
185158
});
186159
}
187160
else
@@ -508,6 +481,64 @@ public static Task DeleteAuthRecordAsync()
508481
File.Delete(Constants.AuthRecordPath);
509482
return Task.CompletedTask;
510483
}
484+
485+
public static PopTokenRequestContext CreatePopTokenRequestContext(IAuthContext authContext)
486+
{
487+
// Creating a httpclient that would handle all pop calls
488+
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)
489+
HttpClient popHttpClient = new(new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true });
490+
491+
// Find the WWW-Authenticate header in the response.
492+
var popMethod = GraphSession.Instance.GraphRequestProofofPossession.HttpMethod ?? HttpMethod.Get;
493+
var popResponse = popHttpClient.SendAsync(new HttpRequestMessage(popMethod, popResourceUri)).Result;
494+
var popChallenge = popResponse.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
495+
var nonceStart = popChallenge.Parameter.IndexOf("nonce=\"") + "nonce=\"".Length;
496+
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
497+
GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);
498+
499+
// Refresh token logic --- start
500+
var popPipelineOptions = new HttpPipelineOptions(new PopClientOptions()
501+
{
502+
503+
});
504+
505+
var _popPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions { ServerCertificateCustomValidationCallback = (_) => true });
506+
GraphSession.Instance.GraphRequestProofofPossession.Request = _popPipeline.CreateRequest();
507+
GraphSession.Instance.GraphRequestProofofPossession.Request.Method = ConvertToAzureRequestMethod(popMethod);
508+
GraphSession.Instance.GraphRequestProofofPossession.Request.Uri.Reset(popResourceUri);
509+
510+
// Refresh token logic --- end
511+
var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce, request: GraphSession.Instance.GraphRequestProofofPossession.Request);
512+
return popContext;
513+
}
514+
public static RequestMethod ConvertToAzureRequestMethod(HttpMethod httpMethod)
515+
{
516+
// Mapping known HTTP methods
517+
switch (httpMethod.Method.ToUpper())
518+
{
519+
case "GET":
520+
return RequestMethod.Get;
521+
case "POST":
522+
return RequestMethod.Post;
523+
case "PUT":
524+
return RequestMethod.Put;
525+
case "DELETE":
526+
return RequestMethod.Delete;
527+
case "HEAD":
528+
return RequestMethod.Head;
529+
case "OPTIONS":
530+
return RequestMethod.Options;
531+
case "PATCH":
532+
return RequestMethod.Patch;
533+
case "TRACE":
534+
return RequestMethod.Trace;
535+
default:
536+
throw new ArgumentException($"Unsupported HTTP method: {httpMethod.Method}");
537+
}
538+
}
539+
}
540+
internal class PopClientOptions : ClientOptions
541+
{
511542
}
512543
internal class PopClientOptions : ClientOptions
513544
{

src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,8 @@ private async Task ProcessRecordAsync()
10231023
try
10241024
{
10251025
PrepareSession();
1026+
GraphSession.Instance.GraphRequestProofofPossession.Uri = Uri;
1027+
GraphSession.Instance.GraphRequestProofofPossession.HttpMethod = GetHttpMethod(Method);
10261028
var client = HttpHelpers.GetGraphHttpClient();
10271029
ValidateRequestUri();
10281030
using (var httpRequestMessage = GetRequest(client, Uri))

src/Authentication/Authentication/Common/GraphSessionInitializer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null)
4747
{
4848
DataStore = dataStore ?? new DiskDataStore(),
4949
RequestContext = new RequestContext(),
50-
GraphOption = graphOptions ?? new GraphOption()
50+
GraphOption = graphOptions ?? new GraphOption(),
51+
GraphRequestProofofPossession = new GraphRequestProofofPossession()
5152
};
5253
}
5354
/// <summary>

src/Authentication/Authentication/Handlers/AuthenticationHandler.cs

+31-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
// ------------------------------------------------------------------------------
44

55

6+
using Azure.Core;
7+
using Azure.Identity;
8+
using Azure.Identity.Broker;
69
using Microsoft.Graph.Authentication;
10+
using Microsoft.Graph.PowerShell.Authentication.Core.Utilities;
711
using Microsoft.Graph.PowerShell.Authentication.Extensions;
12+
using Microsoft.Identity.Client;
813
using System;
914
using System.Collections.Generic;
1015
using System.Linq;
@@ -63,9 +68,24 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag
6368
{
6469
if (AuthenticationProvider != null)
6570
{
66-
var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false);
67-
if (!string.IsNullOrEmpty(accessToken))
68-
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken);
71+
if (AuthenticationHelpers.IsATPoPSupported())
72+
{
73+
GraphSession.Instance.GraphRequestProofofPossession.Request.Method = AuthenticationHelpers.ConvertToAzureRequestMethod(httpRequestMessage.Method);
74+
GraphSession.Instance.GraphRequestProofofPossession.Request.Uri.Reset(httpRequestMessage.RequestUri);
75+
foreach (var header in httpRequestMessage.Headers)
76+
{
77+
GraphSession.Instance.GraphRequestProofofPossession.Request.Headers.Add(header.Key, header.Value.First());
78+
}
79+
80+
var accessToken = GraphSession.Instance.GraphRequestProofofPossession.BrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken).Result;
81+
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Pop", accessToken.Token);
82+
}
83+
else
84+
{
85+
var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false);
86+
if (!string.IsNullOrEmpty(accessToken))
87+
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken);
88+
}
6989
}
7090
}
7191

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

110+
if (AuthenticationHelpers.IsATPoPSupported())
111+
{
112+
var popChallenge = httpResponseMessage.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
113+
var nonceStart = popChallenge.Parameter.IndexOf("nonce=\"") + "nonce=\"".Length;
114+
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
115+
GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);
116+
}
117+
90118
// Authenticate request using auth provider
91119
await AuthenticateRequestAsync(newRequest, additionalRequestInfo, cancellationToken).ConfigureAwait(false);
92120
httpResponseMessage = await base.SendAsync(newRequest, cancellationToken);

src/Authentication/Authentication/Helpers/HttpHelpers.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
// ------------------------------------------------------------------------------
22
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
33
// ------------------------------------------------------------------------------
4+
using Azure.Core;
45
using Microsoft.Graph.Authentication;
56
using Microsoft.Graph.PowerShell.Authentication.Core.Interfaces;
67
using Microsoft.Graph.PowerShell.Authentication.Core.Utilities;
78
using Microsoft.Graph.PowerShell.Authentication.Handlers;
9+
using Microsoft.Identity.Client;
810
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
911
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
1012
using System.Collections.Generic;
1113
using System.Globalization;
14+
using System.Linq;
1215
using System.Net;
1316
using System.Net.Http;
17+
using System.Net.Http.Headers;
1418

1519
namespace Microsoft.Graph.PowerShell.Authentication.Helpers
1620
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
using Azure.Core;
6+
using Azure.Identity;
7+
using System;
8+
using System.IO;
9+
using System.Net.Http;
10+
11+
namespace Microsoft.Graph.PowerShell.Authentication
12+
{
13+
internal class GraphRequestProofofPossession : IGraphRequestProofofPossession
14+
{
15+
public Uri Uri { get; set; }
16+
public HttpMethod HttpMethod { get; set; }
17+
public AccessToken AccessToken { get; set; }
18+
public string ProofofPossessionNonce { get; set; }
19+
public PopTokenRequestContext PopTokenContext { get; set; }
20+
public Request Request { get; set; }
21+
public InteractiveBrowserCredential BrowserCredential { get; set; }
22+
}
23+
24+
}

0 commit comments

Comments
 (0)