55using Microsoft . Extensions . Logging . Abstractions ;
66using Microsoft . Extensions . Options ;
77using System ;
8- using System . Collections . Generic ;
98using System . Linq ;
109using System . Net . Http ;
1110using System . Net . Http . Headers ;
@@ -114,53 +113,46 @@ protected virtual void SetAccessToken(HttpClient client, string accessToken)
114113 client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , accessToken ) ;
115114 }
116115
117- protected virtual async Task < string > GetTokenEndpoint ( IdentityClientConfiguration configuration )
116+ protected virtual async Task < IdentityModelDiscoveryDocumentCacheItem > GetDiscoveryResponse ( IdentityClientConfiguration configuration )
118117 {
119- //TODO: Can use (configuration.Authority + /connect/token) directly?
120-
121118 var tokenEndpointUrlCacheKey = CalculateDiscoveryDocumentCacheKey ( configuration ) ;
122119 var discoveryDocumentCacheItem = await DiscoveryDocumentCache . GetAsync ( tokenEndpointUrlCacheKey ) ;
123120 if ( discoveryDocumentCacheItem == null )
124121 {
125- var discoveryResponse = await GetDiscoveryResponse ( configuration ) ;
122+ DiscoveryDocumentResponse discoveryResponse ;
123+ using ( var httpClient = HttpClientFactory . CreateClient ( HttpClientName ) )
124+ {
125+ var request = new DiscoveryDocumentRequest
126+ {
127+ Address = configuration . Authority ,
128+ Policy =
129+ {
130+ RequireHttps = configuration . RequireHttps
131+ }
132+ } ;
133+ IdentityModelHttpRequestMessageOptions . ConfigureHttpRequestMessage ? . Invoke ( request ) ;
134+ discoveryResponse = await httpClient . GetDiscoveryDocumentAsync ( request ) ;
135+ }
136+
126137 if ( discoveryResponse . IsError )
127138 {
128139 throw new AbpException ( $ "Could not retrieve the OpenId Connect discovery document! " +
129140 $ "ErrorType: { discoveryResponse . ErrorType } . Error: { discoveryResponse . Error } ") ;
130141 }
131142
132- discoveryDocumentCacheItem = new IdentityModelDiscoveryDocumentCacheItem ( discoveryResponse . TokenEndpoint ) ;
143+ discoveryDocumentCacheItem = new IdentityModelDiscoveryDocumentCacheItem ( discoveryResponse . TokenEndpoint , discoveryResponse . DeviceAuthorizationEndpoint ) ;
133144 await DiscoveryDocumentCache . SetAsync ( tokenEndpointUrlCacheKey , discoveryDocumentCacheItem ,
134145 new DistributedCacheEntryOptions
135146 {
136147 AbsoluteExpirationRelativeToNow = TimeSpan . FromSeconds ( configuration . CacheAbsoluteExpiration )
137148 } ) ;
138149 }
139150
140- return discoveryDocumentCacheItem . TokenEndpoint ;
141- }
142-
143- protected virtual async Task < DiscoveryDocumentResponse > GetDiscoveryResponse ( IdentityClientConfiguration configuration )
144- {
145- using ( var httpClient = HttpClientFactory . CreateClient ( HttpClientName ) )
146- {
147- var request = new DiscoveryDocumentRequest
148- {
149- Address = configuration . Authority ,
150- Policy =
151- {
152- RequireHttps = configuration . RequireHttps
153- }
154- } ;
155- IdentityModelHttpRequestMessageOptions . ConfigureHttpRequestMessage ? . Invoke ( request ) ;
156- return await httpClient . GetDiscoveryDocumentAsync ( request ) ;
157- }
151+ return discoveryDocumentCacheItem ;
158152 }
159153
160154 protected virtual async Task < TokenResponse > GetTokenResponse ( IdentityClientConfiguration configuration )
161155 {
162- var tokenEndpoint = await GetTokenEndpoint ( configuration ) ;
163-
164156 using ( var httpClient = HttpClientFactory . CreateClient ( HttpClientName ) )
165157 {
166158 AddHeaders ( httpClient ) ;
@@ -169,25 +161,30 @@ protected virtual async Task<TokenResponse> GetTokenResponse(IdentityClientConfi
169161 {
170162 case OidcConstants . GrantTypes . ClientCredentials :
171163 return await httpClient . RequestClientCredentialsTokenAsync (
172- await CreateClientCredentialsTokenRequestAsync ( tokenEndpoint , configuration ) ,
164+ await CreateClientCredentialsTokenRequestAsync ( configuration ) ,
173165 CancellationTokenProvider . Token
174166 ) ;
175167 case OidcConstants . GrantTypes . Password :
176168 return await httpClient . RequestPasswordTokenAsync (
177- await CreatePasswordTokenRequestAsync ( tokenEndpoint , configuration ) ,
169+ await CreatePasswordTokenRequestAsync ( configuration ) ,
178170 CancellationTokenProvider . Token
179171 ) ;
172+
173+ case OidcConstants . GrantTypes . DeviceCode :
174+ return await RequestDeviceAuthorizationAsync ( httpClient , configuration ) ;
175+
180176 default :
181177 throw new AbpException ( "Grant type was not implemented: " + configuration . GrantType ) ;
182178 }
183179 }
184180 }
185181
186- protected virtual Task < PasswordTokenRequest > CreatePasswordTokenRequestAsync ( string tokenEndpoint , IdentityClientConfiguration configuration )
182+ protected virtual async Task < PasswordTokenRequest > CreatePasswordTokenRequestAsync ( IdentityClientConfiguration configuration )
187183 {
184+ var discoveryResponse = await GetDiscoveryResponse ( configuration ) ;
188185 var request = new PasswordTokenRequest
189186 {
190- Address = tokenEndpoint ,
187+ Address = discoveryResponse . TokenEndpoint ,
191188 Scope = configuration . Scope ,
192189 ClientId = configuration . ClientId ,
193190 ClientSecret = configuration . ClientSecret ,
@@ -197,27 +194,90 @@ protected virtual Task<PasswordTokenRequest> CreatePasswordTokenRequestAsync(str
197194
198195 IdentityModelHttpRequestMessageOptions . ConfigureHttpRequestMessage ? . Invoke ( request ) ;
199196
200- AddParametersToRequestAsync ( configuration , request ) ;
197+ await AddParametersToRequestAsync ( configuration , request ) ;
201198
202- return Task . FromResult ( request ) ;
199+ return request ;
203200 }
204201
205- protected virtual Task < ClientCredentialsTokenRequest > CreateClientCredentialsTokenRequestAsync ( string tokenEndpoint , IdentityClientConfiguration configuration )
202+ protected virtual async Task < ClientCredentialsTokenRequest > CreateClientCredentialsTokenRequestAsync ( IdentityClientConfiguration configuration )
206203 {
204+ var discoveryResponse = await GetDiscoveryResponse ( configuration ) ;
207205 var request = new ClientCredentialsTokenRequest
208206 {
209- Address = tokenEndpoint ,
207+ Address = discoveryResponse . TokenEndpoint ,
210208 Scope = configuration . Scope ,
211209 ClientId = configuration . ClientId ,
212210 ClientSecret = configuration . ClientSecret
213211 } ;
214212 IdentityModelHttpRequestMessageOptions . ConfigureHttpRequestMessage ? . Invoke ( request ) ;
215213
216- AddParametersToRequestAsync ( configuration , request ) ;
214+ await AddParametersToRequestAsync ( configuration , request ) ;
215+
216+ return request ;
217+ }
218+
219+ protected virtual async Task < TokenResponse > RequestDeviceAuthorizationAsync ( HttpClient httpClient , IdentityClientConfiguration configuration )
220+ {
221+ var discoveryResponse = await GetDiscoveryResponse ( configuration ) ;
222+ var request = new DeviceAuthorizationRequest ( )
223+ {
224+ Address = discoveryResponse . DeviceAuthorizationEndpoint ,
225+ Scope = configuration . Scope ,
226+ ClientId = configuration . ClientId ,
227+ ClientSecret = configuration . ClientSecret ,
228+ } ;
229+
230+ IdentityModelHttpRequestMessageOptions . ConfigureHttpRequestMessage ? . Invoke ( request ) ;
231+
232+ await AddParametersToRequestAsync ( configuration , request ) ;
233+
234+ var response = await httpClient . RequestDeviceAuthorizationAsync ( request ) ;
235+ if ( response . IsError )
236+ {
237+ throw new AbpException ( response . ErrorDescription ) ;
238+ }
239+
240+ Logger . LogInformation ( $ "First copy your one-time code: { response . UserCode } ") ;
241+ Logger . LogInformation ( $ "Open { response . VerificationUri } in your browser...") ;
242+
243+ for ( var i = 0 ; i < ( ( response . ExpiresIn ?? 300 ) / response . Interval + 1 ) ; i ++ )
244+ {
245+ await Task . Delay ( response . Interval * 1000 ) ;
246+
247+ var tokenResponse = await httpClient . RequestDeviceTokenAsync ( new DeviceTokenRequest
248+ {
249+ Address = discoveryResponse . TokenEndpoint ,
250+ ClientId = configuration . ClientId ,
251+ ClientSecret = configuration . ClientSecret ,
252+ DeviceCode = response . DeviceCode
253+ } ) ;
217254
218- return Task . FromResult ( request ) ;
255+ if ( tokenResponse . IsError )
256+ {
257+ switch ( tokenResponse . Error )
258+ {
259+ case "slow_down" :
260+ case "authorization_pending" :
261+ break ;
262+
263+ case "expired_token" :
264+ throw new AbpException ( "This 'device_code' has expired. (expired_token)" ) ;
265+
266+ case "access_denied" :
267+ throw new AbpException ( "User denies the request(access_denied)" ) ;
268+ }
269+ }
270+
271+ if ( ! tokenResponse . IsError )
272+ {
273+ return tokenResponse ;
274+ }
275+ }
276+
277+ throw new AbpException ( "Timeout!" ) ;
219278 }
220279
280+
221281 protected virtual Task AddParametersToRequestAsync ( IdentityClientConfiguration configuration , ProtocolRequest request )
222282 {
223283 foreach ( var pair in configuration . Where ( p => p . Key . StartsWith ( "[o]" , StringComparison . OrdinalIgnoreCase ) ) )
0 commit comments