3
3
// ------------------------------------------------------------------------------
4
4
namespace Microsoft . Graph . PowerShell . Authentication . Cmdlets
5
5
{
6
- using Microsoft . Graph . Auth ;
7
- using Microsoft . Graph . PowerShell . Authentication . Helpers ;
8
- using Microsoft . Graph . PowerShell . Authentication . Models ;
9
- using Microsoft . Identity . Client ;
10
6
using System ;
11
7
using System . Collections . Generic ;
12
8
using System . Linq ;
@@ -16,10 +12,20 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
16
12
using System . Threading . Tasks ;
17
13
using System . Net ;
18
14
using System . Globalization ;
19
- using Microsoft . Graph . PowerShell . Authentication . Interfaces ;
20
- using Microsoft . Graph . PowerShell . Authentication . Common ;
15
+ using System . Collections ;
21
16
using System . Security . Cryptography . X509Certificates ;
22
17
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
+
23
29
[ Cmdlet ( VerbsCommunications . Connect , "MgGraph" , DefaultParameterSetName = Constants . UserParameterSet ) ]
24
30
[ Alias ( "Connect-Graph" ) ]
25
31
public class ConnectMgGraph : PSCmdlet , IModuleAssemblyInitializer , IModuleAssemblyCleanup
@@ -70,10 +76,12 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem
70
76
[ Alias ( "EnvironmentName" , "NationalCloud" ) ]
71
77
public string Environment { get ; set ; }
72
78
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" ) ]
74
82
public X509Certificate2 Certificate { get ; set ; }
75
83
76
- private CancellationTokenSource cancellationTokenSource ;
84
+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource ( ) ;
77
85
78
86
private IGraphEnvironment environment ;
79
87
@@ -104,66 +112,102 @@ protected override void EndProcessing()
104
112
protected override void ProcessRecord ( )
105
113
{
106
114
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
113
116
{
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 )
127
129
{
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
+ }
135
134
}
136
- break ;
137
- case Constants . AccessTokenParameterSet :
135
+ else
138
136
{
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 ) ) ;
143
138
}
144
- break ;
139
+ }
145
140
}
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
+ }
146
148
147
- CancellationToken cancellationToken = cancellationTokenSource . Token ;
148
-
149
- try
149
+ private async Task ProcessRecordAsync ( )
150
+ {
151
+ using ( NoSynchronizationContext )
150
152
{
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 )
155
158
{
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 ;
157
191
}
158
- else if ( ParameterSetName == Constants . AppParameterSet )
192
+
193
+ try
159
194
{
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
+ }
162
206
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 >
167
211
{
168
212
{
169
213
typeof ( AuthenticationHandlerOption ) . ToString ( ) ,
@@ -178,49 +222,50 @@ protected override void ProcessRecord()
178
222
}
179
223
} ;
180
224
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 ) ;
185
229
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 ) ;
194
238
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 )
201
243
{
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
+ }
207
256
}
208
- else
257
+ catch ( Exception ex )
209
258
{
210
- throw authEx . InnerException ?? authEx ;
259
+ throw ex . InnerException ?? ex ;
211
260
}
212
- }
213
- catch ( Exception ex )
214
- {
215
- throw ex . InnerException ?? ex ;
216
- }
217
261
218
- WriteObject ( "Welcome To Microsoft Graph!" ) ;
262
+ WriteObject ( "Welcome To Microsoft Graph!" ) ;
263
+ }
219
264
}
220
265
221
266
protected override void StopProcessing ( )
222
267
{
223
- cancellationTokenSource . Cancel ( ) ;
268
+ _cancellationTokenSource . Cancel ( ) ;
224
269
base . StopProcessing ( ) ;
225
270
}
226
271
0 commit comments