33
44using System . ClientModel . Primitives ;
55using System . Collections . Generic ;
6+ using System . Linq ;
67using System . Net . Http ;
78using System . Net . Http . Headers ;
89using System . Text ;
@@ -25,21 +26,39 @@ public void SampleUsage()
2526 client . Get ( ) ;
2627 }
2728
29+ [ Test ]
30+ public void SupportsNoServiceLevelAuth ( )
31+ {
32+ // usage for TokenProvider2 abstract type
33+ AuthenticationTokenProvider provider = new ClientCredentialTokenProvider ( "myClientId" , "myClientSecret" ) ;
34+ var client = new NoAuthClient ( new Uri ( "http://localhost" ) , provider ) ;
35+ client . Get ( ) ;
36+ }
37+
2838 public class FooClient
2939 {
3040 // Generated from the TypeSpec spec.
31- private readonly Dictionary < string , object > [ ] flows = [
41+
42+ private static readonly string readScope = "read" ;
43+ private readonly Dictionary < string , object > [ ] serviceFlows = [
3244 new Dictionary < string , object > {
3345 { GetTokenOptions . ScopesPropertyName , new string [ ] { "baselineScope" } } ,
3446 { GetTokenOptions . TokenUrlPropertyName , "https://myauthserver.com/token" } ,
3547 { GetTokenOptions . RefreshUrlPropertyName , "https://myauthserver.com/refresh" }
3648 }
3749 ] ;
3850
51+ private readonly Dictionary < string , object > [ ] getOperationsFlows = [
52+ new Dictionary < string , object > {
53+ { GetTokenOptions . ScopesPropertyName , new string [ ] { "baselineScope" , readScope } } ,
54+ { GetTokenOptions . TokenUrlPropertyName , "https://myauthserver.com/token" } ,
55+ { GetTokenOptions . RefreshUrlPropertyName , "https://myauthserver.com/refresh" }
56+ }
57+ ] ;
58+
3959 private readonly IReadOnlyDictionary < string , object > _emptyProperties = new Dictionary < string , object > ( ) ;
4060
4161 private ClientPipeline _pipeline ;
42- private static readonly string [ ] readScope = [ "read" ] ;
4362
4463 public FooClient ( Uri uri , ApiKeyCredential credential )
4564 {
@@ -62,7 +81,7 @@ public FooClient(Uri uri, AuthenticationTokenProvider credential)
6281 } ) ;
6382 ClientPipeline pipeline = ClientPipeline . Create ( options ,
6483 perCallPolicies : ReadOnlySpan < PipelinePolicy > . Empty ,
65- perTryPolicies : [ new BearerTokenPolicy ( credential , flows ) ] ,
84+ perTryPolicies : [ new BearerTokenPolicy ( credential , serviceFlows ) ] ,
6685 beforeTransportPolicies : ReadOnlySpan < PipelinePolicy > . Empty ) ;
6786 _pipeline = pipeline ;
6887 }
@@ -71,7 +90,7 @@ public ClientResult Get()
7190 {
7291 var message = _pipeline . CreateMessage ( ) ;
7392 message . ResponseClassifier = PipelineMessageClassifier . Create ( [ 200 ] ) ;
74- message . SetProperty ( typeof ( GetTokenOptions ) , new GetTokenOptions ( readScope , _emptyProperties ) ) ;
93+ message . SetProperty ( typeof ( GetTokenOptions ) , getOperationsFlows ) ;
7594
7695 PipelineRequest request = message . Request ;
7796 request . Method = "GET" ;
@@ -81,6 +100,53 @@ public ClientResult Get()
81100 }
82101 }
83102
103+ public class NoAuthClient
104+ {
105+ private readonly IReadOnlyDictionary < string , object > _emptyProperties = new Dictionary < string , object > ( ) ;
106+
107+ private ClientPipeline _pipeline ;
108+
109+ public NoAuthClient ( Uri uri , ApiKeyCredential credential )
110+ {
111+ var options = new ClientPipelineOptions ( ) ;
112+ options . Transport = new MockPipelineTransport ( "foo" , m => new MockPipelineResponse ( 200 ) ) ;
113+ ClientPipeline pipeline = ClientPipeline . Create ( options ,
114+ perCallPolicies : ReadOnlySpan < PipelinePolicy > . Empty ,
115+ perTryPolicies : [ ApiKeyAuthenticationPolicy . CreateBasicAuthorizationPolicy ( credential ) ] ,
116+ beforeTransportPolicies : ReadOnlySpan < PipelinePolicy > . Empty ) ;
117+ _pipeline = pipeline ;
118+ }
119+
120+ public NoAuthClient ( Uri uri , AuthenticationTokenProvider credential )
121+ {
122+ var options = new ClientPipelineOptions ( ) ;
123+ options . Transport = new MockPipelineTransport ( "foo" ,
124+ m =>
125+ {
126+ // Assert that the request has no authentication headers
127+ Assert . IsFalse ( m . Request . Headers . TryGetValue ( "Authorization" , out _ ) , "Request should not have an Authorization header." ) ;
128+ return new MockPipelineResponse ( 200 ) ;
129+ } ) ;
130+ ClientPipeline pipeline = ClientPipeline . Create ( options ,
131+ perCallPolicies : ReadOnlySpan < PipelinePolicy > . Empty ,
132+ perTryPolicies : [ new BearerTokenPolicy ( credential , [ _emptyProperties ] ) ] ,
133+ beforeTransportPolicies : ReadOnlySpan < PipelinePolicy > . Empty ) ;
134+ _pipeline = pipeline ;
135+ }
136+
137+ public ClientResult Get ( )
138+ {
139+ var message = _pipeline . CreateMessage ( ) ;
140+ message . ResponseClassifier = PipelineMessageClassifier . Create ( [ 200 ] ) ;
141+
142+ PipelineRequest request = message . Request ;
143+ request . Method = "GET" ;
144+ request . Uri = new Uri ( "https://localhost/noAuth" ) ;
145+ _pipeline . Send ( message ) ;
146+ return ClientResult . FromResponse ( message . Response ! ) ;
147+ }
148+ }
149+
84150 public class ClientCredentialTokenProvider : AuthenticationTokenProvider
85151 {
86152 private string _clientId ;
@@ -148,8 +214,9 @@ public override async ValueTask<AuthenticationToken> GetTokenAsync(GetTokenOptio
148214 properties . TryGetValue ( GetTokenOptions . TokenUrlPropertyName , out var tokenUri ) && tokenUri is string tokenUriValue &&
149215 properties . TryGetValue ( GetTokenOptions . RefreshUrlPropertyName , out var refreshUri ) && refreshUri is string refreshUriValue )
150216 {
151- return new GetTokenOptions ( scopeArray , new Dictionary < string , object >
217+ return new GetTokenOptions ( new Dictionary < string , object >
152218 {
219+ { GetTokenOptions . ScopesPropertyName , new ReadOnlyMemory < string > ( scopeArray ) } ,
153220 { GetTokenOptions . TokenUrlPropertyName , tokenUriValue } ,
154221 { GetTokenOptions . RefreshUrlPropertyName , refreshUriValue }
155222 } ) ;
@@ -169,12 +236,13 @@ internal async ValueTask<AuthenticationToken> GetAccessTokenInternal(bool async,
169236 var authBytes = System . Text . Encoding . ASCII . GetBytes ( $ "{ _clientId } :{ _clientSecret } ") ;
170237 var authHeader = Convert . ToBase64String ( authBytes ) ;
171238 request . Headers . Authorization = new AuthenticationHeaderValue ( "Basic" , authHeader ) ;
239+ var scopes = ExtractScopes ( properties . Properties ) ;
172240
173241 // Create form content
174242 var formContent = new FormUrlEncodedContent (
175243 [
176244 new KeyValuePair < string , string > ( "grant_type" , "client_credentials" ) ,
177- new KeyValuePair < string , string > ( "scope" , string . Join ( " " , properties . Scopes . Span . ToArray ( ) ) )
245+ new KeyValuePair < string , string > ( "scope" , string . Join ( " " , scopes . ToArray ( ) ) )
178246 ] ) ;
179247
180248 request . Content = formContent ;
@@ -201,6 +269,24 @@ await _client.SendAsync(request) :
201269
202270 return new AuthenticationToken ( accessToken ! , tokenType ! , expiresOn , refreshOn ) ;
203271 }
272+
273+ private static ReadOnlyMemory < string > ExtractScopes ( IReadOnlyDictionary < string , object > properties )
274+ {
275+ if ( ! properties . TryGetValue ( GetTokenOptions . ScopesPropertyName , out var scopesValue ) || scopesValue is null )
276+ {
277+ return ReadOnlyMemory < string > . Empty ;
278+ }
279+
280+ return scopesValue switch
281+ {
282+ ReadOnlyMemory < string > memory => memory ,
283+ Memory < string > memory => memory ,
284+ string [ ] array => new ReadOnlyMemory < string > ( array ) ,
285+ ICollection < string > collection => new ReadOnlyMemory < string > ( [ .. collection ] ) ,
286+ IEnumerable < string > enumerable => new ReadOnlyMemory < string > ( [ .. enumerable ] ) ,
287+ _ => ReadOnlyMemory < string > . Empty
288+ } ;
289+ }
204290 }
205291
206292 public class ClientCredentialToken : AuthenticationToken
0 commit comments