@@ -6,7 +6,6 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
66 using Microsoft . Graph . Auth ;
77 using Microsoft . Graph . PowerShell . Authentication . Helpers ;
88 using Microsoft . Graph . PowerShell . Authentication . Models ;
9- using Microsoft . Graph . PowerShell . Authentication . Extensions ;
109 using Microsoft . Identity . Client ;
1110 using System ;
1211 using System . Collections . Generic ;
@@ -15,30 +14,50 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
1514 using System . Net . Http ;
1615 using System . Threading ;
1716 using System . Threading . Tasks ;
17+ using System . Net ;
18+ using System . Globalization ;
1819
1920 [ Cmdlet ( VerbsCommunications . Connect , "Graph" , DefaultParameterSetName = Constants . UserParameterSet ) ]
2021 public class ConnectGraph : PSCmdlet , IModuleAssemblyInitializer , IModuleAssemblyCleanup
2122 {
22- [ Parameter ( ParameterSetName = Constants . UserParameterSet , Position = 1 , HelpMessage = "An array of delegated permissions to consent to." ) ]
23+ [ Parameter ( ParameterSetName = Constants . UserParameterSet ,
24+ Position = 1 ,
25+ HelpMessage = "An array of delegated permissions to consent to." ) ]
2326 public string [ ] Scopes { get ; set ; }
2427
25- [ Parameter ( ParameterSetName = Constants . AppParameterSet , Position = 1 , Mandatory = true , HelpMessage = "The client id of your application." ) ]
28+ [ Parameter ( ParameterSetName = Constants . AppParameterSet ,
29+ Position = 1 ,
30+ Mandatory = true ,
31+ HelpMessage = "The client id of your application." ) ]
2632 public string ClientId { get ; set ; }
2733
28- [ Parameter ( ParameterSetName = Constants . AppParameterSet , Position = 2 , HelpMessage = "The name of your certificate. The Certificate will be retrieved from the current user's certificate store." ) ]
34+ [ Parameter ( ParameterSetName = Constants . AppParameterSet ,
35+ Position = 2 ,
36+ HelpMessage = "The name of your certificate. The Certificate will be retrieved from the current user's certificate store." ) ]
2937 public string CertificateName { get ; set ; }
3038
31- [ Parameter ( ParameterSetName = Constants . AppParameterSet , Position = 3 , HelpMessage = "The thumbprint of your certificate. The Certificate will be retrieved from the current user's certificate store." ) ]
39+ [ Parameter ( ParameterSetName = Constants . AppParameterSet ,
40+ Position = 3 ,
41+ HelpMessage = "The thumbprint of your certificate. The Certificate will be retrieved from the current user's certificate store." ) ]
3242 public string CertificateThumbprint { get ; set ; }
33-
34- [ Parameter ( Position = 4 , HelpMessage = "The id of the tenant to connect to." ) ]
43+
44+ [ Parameter ( ParameterSetName = Constants . AccessTokenParameterSet ,
45+ Position = 1 ,
46+ HelpMessage = "Specifies a bearer token for Microsoft Graph service. Access tokens do timeout and you'll have to handle their refresh." ) ]
47+ public string AccessToken { get ; set ; }
48+
49+ [ Parameter ( Position = 4 ,
50+ HelpMessage = "The id of the tenant to connect to." ) ]
3551 public string TenantId { get ; set ; }
3652
37- [ Parameter ( Position = 5 , HelpMessage = "Forces the command to get a new access token silently." ) ]
53+ [ Parameter ( Position = 5 ,
54+ HelpMessage = "Forces the command to get a new access token silently." ) ]
3855 public SwitchParameter ForceRefresh { get ; set ; }
3956
40- [ Parameter ( Mandatory = false , HelpMessage = "Determines the scope of authentication context. This accepts `Process` for the current process, or `CurrentUser` for all sessions started by user." ) ]
57+ [ Parameter ( Mandatory = false ,
58+ HelpMessage = "Determines the scope of authentication context. This accepts `Process` for the current process, or `CurrentUser` for all sessions started by user." ) ]
4159 public ContextScope ContextScope { get ; set ; }
60+
4261 private CancellationTokenSource cancellationTokenSource ;
4362
4463 protected override void BeginProcessing ( )
@@ -55,41 +74,57 @@ protected override void EndProcessing()
5574 protected override void ProcessRecord ( )
5675 {
5776 base . ProcessRecord ( ) ;
77+ IAuthContext authContext = new AuthContext { TenantId = TenantId } ;
78+ cancellationTokenSource = new CancellationTokenSource ( ) ;
5879
59- IAuthContext authConfig = new AuthContext { TenantId = TenantId } ;
60-
61- if ( ParameterSetName == Constants . UserParameterSet )
80+ switch ( ParameterSetName )
6281 {
63- // 2 mins timeout. 1 min < HTTP timeout.
64- TimeSpan authTimeout = new TimeSpan ( 0 , 0 , Constants . MaxDeviceCodeTimeOut ) ;
65- cancellationTokenSource = new CancellationTokenSource ( authTimeout ) ;
66- authConfig . AuthType = AuthenticationType . Delegated ;
67- authConfig . Scopes = Scopes ?? new string [ ] { "User.Read" } ;
68- // Default to CurrentUser but allow the customer to change this via `ContextScope` param.
69- authConfig . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . CurrentUser ;
70- }
71- else
72- {
73- cancellationTokenSource = new CancellationTokenSource ( ) ;
74- authConfig . AuthType = AuthenticationType . AppOnly ;
75- authConfig . ClientId = ClientId ;
76- authConfig . CertificateThumbprint = CertificateThumbprint ;
77- authConfig . CertificateName = CertificateName ;
78- // Default to Process but allow the customer to change this via `ContextScope` param.
79- authConfig . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . Process ;
82+ case Constants . UserParameterSet :
83+ {
84+ // 2 mins timeout. 1 min < HTTP timeout.
85+ TimeSpan authTimeout = new TimeSpan ( 0 , 0 , Constants . MaxDeviceCodeTimeOut ) ;
86+ cancellationTokenSource = new CancellationTokenSource ( authTimeout ) ;
87+ authContext . AuthType = AuthenticationType . Delegated ;
88+ authContext . Scopes = Scopes ?? new string [ ] { "User.Read" } ;
89+ // Default to CurrentUser but allow the customer to change this via `ContextScope` param.
90+ authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . CurrentUser ;
91+ }
92+ break ;
93+ case Constants . AppParameterSet :
94+ {
95+ authContext . AuthType = AuthenticationType . AppOnly ;
96+ authContext . ClientId = ClientId ;
97+ authContext . CertificateThumbprint = CertificateThumbprint ;
98+ authContext . CertificateName = CertificateName ;
99+ // Default to Process but allow the customer to change this via `ContextScope` param.
100+ authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . Process ;
101+ }
102+ break ;
103+ case Constants . AccessTokenParameterSet :
104+ {
105+ authContext . AuthType = AuthenticationType . UserProvidedAccessToken ;
106+ authContext . ContextScope = ContextScope . Process ;
107+ // Store user provided access token to a session object.
108+ GraphSession . Instance . UserProvidedToken = new NetworkCredential ( string . Empty , AccessToken ) . SecurePassword ;
109+ }
110+ break ;
80111 }
81112
82113 CancellationToken cancellationToken = cancellationTokenSource . Token ;
83114
84115 try
85116 {
86117 // Gets a static instance of IAuthenticationProvider when the client app hasn't changed.
87- IAuthenticationProvider authProvider = AuthenticationHelpers . GetAuthProvider ( authConfig ) ;
118+ IAuthenticationProvider authProvider = AuthenticationHelpers . GetAuthProvider ( authContext ) ;
88119 IClientApplicationBase clientApplication = null ;
89120 if ( ParameterSetName == Constants . UserParameterSet )
121+ {
90122 clientApplication = ( authProvider as DeviceCodeProvider ) . ClientApplication ;
91- else
123+ }
124+ else if ( ParameterSetName == Constants . AppParameterSet )
125+ {
92126 clientApplication = ( authProvider as ClientCredentialProvider ) . ClientApplication ;
127+ }
93128
94129 // Incremental scope consent without re-instantiating the auth provider. We will use a static instance.
95130 GraphRequestContext graphRequestContext = new GraphRequestContext ( ) ;
@@ -102,7 +137,7 @@ protected override void ProcessRecord()
102137 {
103138 AuthenticationProviderOption = new AuthenticationProviderOption
104139 {
105- Scopes = authConfig . Scopes ,
140+ Scopes = authContext . Scopes ,
106141 ForceRefresh = ForceRefresh
107142 }
108143 }
@@ -114,24 +149,32 @@ protected override void ProcessRecord()
114149 httpRequestMessage . Properties . Add ( typeof ( GraphRequestContext ) . ToString ( ) , graphRequestContext ) ;
115150 authProvider . AuthenticateRequestAsync ( httpRequestMessage ) . GetAwaiter ( ) . GetResult ( ) ;
116151
117- var accounts = clientApplication . GetAccountsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
118- var account = accounts . FirstOrDefault ( ) ;
119-
120- JwtPayload jwtPayload = JwtHelpers . DecodeToObject < JwtPayload > ( httpRequestMessage . Headers . Authorization ? . Parameter ) ;
121- authConfig . Scopes = jwtPayload ? . Scp ? . Split ( ' ' ) ?? jwtPayload ? . Roles ;
122- authConfig . TenantId = jwtPayload ? . Tid ?? account ? . HomeAccountId ? . TenantId ;
123- authConfig . AppName = jwtPayload ? . AppDisplayname ;
124- authConfig . Account = jwtPayload ? . Upn ?? account ? . Username ;
152+ IAccount account = null ;
153+ if ( clientApplication != null )
154+ {
155+ // Only get accounts when we are using MSAL to get an access token.
156+ IEnumerable < IAccount > accounts = clientApplication . GetAccountsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
157+ account = accounts . FirstOrDefault ( ) ;
158+ }
159+ DecodeJWT ( httpRequestMessage . Headers . Authorization ? . Parameter , account , ref authContext ) ;
125160
126161 // Save auth context to session state.
127- GraphSession . Instance . AuthContext = authConfig ;
162+ GraphSession . Instance . AuthContext = authContext ;
128163 }
129164 catch ( AuthenticationException authEx )
130165 {
131166 if ( ( authEx . InnerException is TaskCanceledException ) && cancellationToken . IsCancellationRequested )
132- throw new Exception ( $ "Device code terminal timed-out after { Constants . MaxDeviceCodeTimeOut } seconds. Please try again.") ;
167+ {
168+ // DeviceCodeTimeout
169+ throw new Exception ( string . Format (
170+ CultureInfo . CurrentCulture ,
171+ ErrorConstants . Message . DeviceCodeTimeout ,
172+ Constants . MaxDeviceCodeTimeOut ) ) ;
173+ }
133174 else
175+ {
134176 throw authEx . InnerException ?? authEx ;
177+ }
135178 }
136179 catch ( Exception ex )
137180 {
@@ -173,6 +216,24 @@ private void ThrowParameterError(string parameterName)
173216 ) ;
174217 }
175218
219+ private void DecodeJWT ( string token , IAccount account , ref IAuthContext authContext )
220+ {
221+ JwtPayload jwtPayload = JwtHelpers . DecodeToObject < JwtPayload > ( token ) ;
222+ if ( jwtPayload == null && authContext . AuthType == AuthenticationType . UserProvidedAccessToken )
223+ {
224+ throw new Exception ( string . Format (
225+ CultureInfo . CurrentCulture ,
226+ ErrorConstants . Message . InvalidUserProvidedToken ,
227+ nameof ( AccessToken ) ) ) ;
228+ }
229+
230+ authContext . ClientId = jwtPayload ? . Appid ?? authContext . ClientId ;
231+ authContext . Scopes = jwtPayload ? . Scp ? . Split ( ' ' ) ?? jwtPayload ? . Roles ;
232+ authContext . TenantId = jwtPayload ? . Tid ?? account ? . HomeAccountId ? . TenantId ;
233+ authContext . AppName = jwtPayload ? . AppDisplayname ;
234+ authContext . Account = jwtPayload ? . Upn ?? account ? . Username ;
235+ }
236+
176237 /// <summary>
177238 /// Globally initializes GraphSession.
178239 /// </summary>
0 commit comments