Skip to content

Commit 044f6a4

Browse files
authored
Merge branch 'dev' into po/weeklyOpenAPIDocsUpdate
2 parents 3c7f0b1 + 2da2646 commit 044f6a4

File tree

7 files changed

+1239
-246
lines changed

7 files changed

+1239
-246
lines changed

src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs

+133-88
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
// ------------------------------------------------------------------------------
44
namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
55
{
6-
using Microsoft.Graph.Auth;
7-
using Microsoft.Graph.PowerShell.Authentication.Helpers;
8-
using Microsoft.Graph.PowerShell.Authentication.Models;
9-
using Microsoft.Identity.Client;
106
using System;
117
using System.Collections.Generic;
128
using System.Linq;
@@ -16,10 +12,20 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
1612
using System.Threading.Tasks;
1713
using System.Net;
1814
using System.Globalization;
19-
using Microsoft.Graph.PowerShell.Authentication.Interfaces;
20-
using Microsoft.Graph.PowerShell.Authentication.Common;
15+
using System.Collections;
2116
using System.Security.Cryptography.X509Certificates;
2217

18+
using Identity.Client;
19+
20+
using Microsoft.Graph.Auth;
21+
using Microsoft.Graph.PowerShell.Authentication.Helpers;
22+
using Microsoft.Graph.PowerShell.Authentication.Models;
23+
24+
using Interfaces;
25+
using Common;
26+
27+
using static Helpers.AsyncHelpers;
28+
2329
[Cmdlet(VerbsCommunications.Connect, "MgGraph", DefaultParameterSetName = Constants.UserParameterSet)]
2430
[Alias("Connect-Graph")]
2531
public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssemblyCleanup
@@ -70,10 +76,12 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem
7076
[Alias("EnvironmentName", "NationalCloud")]
7177
public string Environment { get; set; }
7278

73-
[Parameter(ParameterSetName = Constants.AppParameterSet, Mandatory = false, HelpMessage = "An x509 Certificate supplied during invocation")]
79+
[Parameter(Mandatory = false,
80+
ParameterSetName = Constants.AppParameterSet,
81+
HelpMessage = "An x509 Certificate supplied during invocation")]
7482
public X509Certificate2 Certificate { get; set; }
7583

76-
private CancellationTokenSource cancellationTokenSource;
84+
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
7785

7886
private IGraphEnvironment environment;
7987

@@ -104,66 +112,102 @@ protected override void EndProcessing()
104112
protected override void ProcessRecord()
105113
{
106114
base.ProcessRecord();
107-
IAuthContext authContext = new AuthContext { TenantId = TenantId };
108-
cancellationTokenSource = new CancellationTokenSource();
109-
// Set selected environment to the session object.
110-
GraphSession.Instance.Environment = environment;
111-
112-
switch (ParameterSetName)
115+
try
113116
{
114-
case Constants.UserParameterSet:
115-
{
116-
// 2 mins timeout. 1 min < HTTP timeout.
117-
TimeSpan authTimeout = new TimeSpan(0, 0, Constants.MaxDeviceCodeTimeOut);
118-
cancellationTokenSource = new CancellationTokenSource(authTimeout);
119-
authContext.AuthType = AuthenticationType.Delegated;
120-
string[] processedScopes = ProcessScopes(Scopes);
121-
authContext.Scopes = processedScopes.Length == 0 ? new string[] { "User.Read" } : processedScopes;
122-
// Default to CurrentUser but allow the customer to change this via `ContextScope` param.
123-
authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.CurrentUser;
124-
}
125-
break;
126-
case Constants.AppParameterSet:
117+
using (var asyncCommandRuntime = new CustomAsyncCommandRuntime(this, _cancellationTokenSource.Token))
118+
{
119+
asyncCommandRuntime.Wait(ProcessRecordAsync(), _cancellationTokenSource.Token);
120+
}
121+
}
122+
catch (AggregateException aggregateException)
123+
{
124+
// unroll the inner exceptions to get the root cause
125+
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
126+
{
127+
var errorRecords = innerException.Data;
128+
if (errorRecords.Count < 1)
127129
{
128-
authContext.AuthType = AuthenticationType.AppOnly;
129-
authContext.ClientId = ClientId;
130-
authContext.CertificateThumbprint = CertificateThumbprint;
131-
authContext.CertificateName = CertificateName;
132-
authContext.Certificate = Certificate;
133-
// Default to Process but allow the customer to change this via `ContextScope` param.
134-
authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.Process;
130+
foreach (DictionaryEntry dictionaryEntry in errorRecords)
131+
{
132+
WriteError((ErrorRecord)dictionaryEntry.Value);
133+
}
135134
}
136-
break;
137-
case Constants.AccessTokenParameterSet:
135+
else
138136
{
139-
authContext.AuthType = AuthenticationType.UserProvidedAccessToken;
140-
authContext.ContextScope = ContextScope.Process;
141-
// Store user provided access token to a session object.
142-
GraphSession.Instance.UserProvidedToken = new NetworkCredential(string.Empty, AccessToken).SecurePassword;
137+
WriteError(new ErrorRecord(innerException, string.Empty, ErrorCategory.NotSpecified, null));
143138
}
144-
break;
139+
}
145140
}
141+
catch (Exception exception) when (exception as PipelineStoppedException == null ||
142+
(exception as PipelineStoppedException).InnerException != null)
143+
{
144+
// Write exception out to error channel.
145+
WriteError(new ErrorRecord(exception, string.Empty, ErrorCategory.NotSpecified, null));
146+
}
147+
}
146148

147-
CancellationToken cancellationToken = cancellationTokenSource.Token;
148-
149-
try
149+
private async Task ProcessRecordAsync()
150+
{
151+
using (NoSynchronizationContext)
150152
{
151-
// Gets a static instance of IAuthenticationProvider when the client app hasn't changed.
152-
IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(authContext);
153-
IClientApplicationBase clientApplication = null;
154-
if (ParameterSetName == Constants.UserParameterSet)
153+
IAuthContext authContext = new AuthContext { TenantId = TenantId };
154+
// Set selected environment to the session object.
155+
GraphSession.Instance.Environment = environment;
156+
157+
switch (ParameterSetName)
155158
{
156-
clientApplication = (authProvider as DeviceCodeProvider).ClientApplication;
159+
case Constants.UserParameterSet:
160+
{
161+
// 2 mins timeout. 1 min < HTTP timeout.
162+
TimeSpan authTimeout = new TimeSpan(0, 0, Constants.MaxDeviceCodeTimeOut);
163+
// To avoid re-initializing the tokenSource, use CancelAfter
164+
_cancellationTokenSource.CancelAfter(authTimeout);
165+
authContext.AuthType = AuthenticationType.Delegated;
166+
string[] processedScopes = ProcessScopes(Scopes);
167+
authContext.Scopes = processedScopes.Length == 0 ? new string[] { "User.Read" } : processedScopes;
168+
// Default to CurrentUser but allow the customer to change this via `ContextScope` param.
169+
authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.CurrentUser;
170+
}
171+
break;
172+
case Constants.AppParameterSet:
173+
{
174+
authContext.AuthType = AuthenticationType.AppOnly;
175+
authContext.ClientId = ClientId;
176+
authContext.CertificateThumbprint = CertificateThumbprint;
177+
authContext.CertificateName = CertificateName;
178+
authContext.Certificate = Certificate;
179+
// Default to Process but allow the customer to change this via `ContextScope` param.
180+
authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.Process;
181+
}
182+
break;
183+
case Constants.AccessTokenParameterSet:
184+
{
185+
authContext.AuthType = AuthenticationType.UserProvidedAccessToken;
186+
authContext.ContextScope = ContextScope.Process;
187+
// Store user provided access token to a session object.
188+
GraphSession.Instance.UserProvidedToken = new NetworkCredential(string.Empty, AccessToken).SecurePassword;
189+
}
190+
break;
157191
}
158-
else if (ParameterSetName == Constants.AppParameterSet)
192+
193+
try
159194
{
160-
clientApplication = (authProvider as ClientCredentialProvider).ClientApplication;
161-
}
195+
// Gets a static instance of IAuthenticationProvider when the client app hasn't changed.
196+
IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(authContext);
197+
IClientApplicationBase clientApplication = null;
198+
if (ParameterSetName == Constants.UserParameterSet)
199+
{
200+
clientApplication = (authProvider as DeviceCodeProvider).ClientApplication;
201+
}
202+
else if (ParameterSetName == Constants.AppParameterSet)
203+
{
204+
clientApplication = (authProvider as ClientCredentialProvider).ClientApplication;
205+
}
162206

163-
// Incremental scope consent without re-instantiating the auth provider. We will use a static instance.
164-
GraphRequestContext graphRequestContext = new GraphRequestContext();
165-
graphRequestContext.CancellationToken = cancellationToken;
166-
graphRequestContext.MiddlewareOptions = new Dictionary<string, IMiddlewareOption>
207+
// Incremental scope consent without re-instantiating the auth provider. We will use a static instance.
208+
GraphRequestContext graphRequestContext = new GraphRequestContext();
209+
graphRequestContext.CancellationToken = _cancellationTokenSource.Token;
210+
graphRequestContext.MiddlewareOptions = new Dictionary<string, IMiddlewareOption>
167211
{
168212
{
169213
typeof(AuthenticationHandlerOption).ToString(),
@@ -178,49 +222,50 @@ protected override void ProcessRecord()
178222
}
179223
};
180224

181-
// Trigger consent.
182-
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
183-
httpRequestMessage.Properties.Add(typeof(GraphRequestContext).ToString(), graphRequestContext);
184-
authProvider.AuthenticateRequestAsync(httpRequestMessage).GetAwaiter().GetResult();
225+
// Trigger consent.
226+
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
227+
httpRequestMessage.Properties.Add(typeof(GraphRequestContext).ToString(), graphRequestContext);
228+
await authProvider.AuthenticateRequestAsync(httpRequestMessage);
185229

186-
IAccount account = null;
187-
if (clientApplication != null)
188-
{
189-
// Only get accounts when we are using MSAL to get an access token.
190-
IEnumerable<IAccount> accounts = clientApplication.GetAccountsAsync().GetAwaiter().GetResult();
191-
account = accounts.FirstOrDefault();
192-
}
193-
DecodeJWT(httpRequestMessage.Headers.Authorization?.Parameter, account, ref authContext);
230+
IAccount account = null;
231+
if (clientApplication != null)
232+
{
233+
// Only get accounts when we are using MSAL to get an access token.
234+
IEnumerable<IAccount> accounts = clientApplication.GetAccountsAsync().GetAwaiter().GetResult();
235+
account = accounts.FirstOrDefault();
236+
}
237+
DecodeJWT(httpRequestMessage.Headers.Authorization?.Parameter, account, ref authContext);
194238

195-
// Save auth context to session state.
196-
GraphSession.Instance.AuthContext = authContext;
197-
}
198-
catch (AuthenticationException authEx)
199-
{
200-
if ((authEx.InnerException is TaskCanceledException) && cancellationToken.IsCancellationRequested)
239+
// Save auth context to session state.
240+
GraphSession.Instance.AuthContext = authContext;
241+
}
242+
catch (AuthenticationException authEx)
201243
{
202-
// DeviceCodeTimeout
203-
throw new Exception(string.Format(
204-
CultureInfo.CurrentCulture,
205-
ErrorConstants.Message.DeviceCodeTimeout,
206-
Constants.MaxDeviceCodeTimeOut));
244+
if ((authEx.InnerException is TaskCanceledException) && _cancellationTokenSource.Token.IsCancellationRequested)
245+
{
246+
// DeviceCodeTimeout
247+
throw new Exception(string.Format(
248+
CultureInfo.CurrentCulture,
249+
ErrorConstants.Message.DeviceCodeTimeout,
250+
Constants.MaxDeviceCodeTimeOut));
251+
}
252+
else
253+
{
254+
throw authEx.InnerException ?? authEx;
255+
}
207256
}
208-
else
257+
catch (Exception ex)
209258
{
210-
throw authEx.InnerException ?? authEx;
259+
throw ex.InnerException ?? ex;
211260
}
212-
}
213-
catch (Exception ex)
214-
{
215-
throw ex.InnerException ?? ex;
216-
}
217261

218-
WriteObject("Welcome To Microsoft Graph!");
262+
WriteObject("Welcome To Microsoft Graph!");
263+
}
219264
}
220265

221266
protected override void StopProcessing()
222267
{
223-
cancellationTokenSource.Cancel();
268+
_cancellationTokenSource.Cancel();
224269
base.StopProcessing();
225270
}
226271

0 commit comments

Comments
 (0)