Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 87c31c5

Browse files
committed
Switch to IUrlEncoder, introduce AddAuthentication
1 parent 30d350d commit 87c31c5

File tree

30 files changed

+106
-80
lines changed

30 files changed

+106
-80
lines changed

samples/CookieSample/Startup.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ public class Startup
1111
{
1212
public void ConfigureServices(IServiceCollection services)
1313
{
14-
services.AddWebEncoders();
15-
services.AddDataProtection();
14+
services.AddAuthentication();
1615
}
1716

1817
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)

samples/CookieSessionSample/Startup.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ public class Startup
1212
{
1313
public void ConfigureServices(IServiceCollection services)
1414
{
15-
services.AddWebEncoders();
16-
services.AddDataProtection();
15+
services.AddAuthentication();
1716
}
1817

1918
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)

samples/OpenIdConnectSample/Startup.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ public class Startup
1313
{
1414
public void ConfigureServices(IServiceCollection services)
1515
{
16-
services.AddWebEncoders();
17-
services.AddDataProtection();
16+
services.AddAuthentication();
1817
services.Configure<ExternalAuthenticationOptions>(options =>
1918
{
2019
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

samples/SocialSample/Startup.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ public class Startup
1919
{
2020
public void ConfigureServices(IServiceCollection services)
2121
{
22-
services.AddWebEncoders();
23-
services.AddDataProtection();
22+
services.AddAuthentication();
2423
services.Configure<ExternalAuthenticationOptions>(options =>
2524
{
2625
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
@@ -139,13 +138,13 @@ dnx . web
139138
OnGetUserInformationAsync = async (context) =>
140139
{
141140
// Get the GitHub user
142-
HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
141+
var userRequest = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
143142
userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
144143
userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
145-
HttpResponseMessage userResponse = await context.Backchannel.SendAsync(userRequest, context.HttpContext.RequestAborted);
144+
var userResponse = await context.Backchannel.SendAsync(userRequest, context.HttpContext.RequestAborted);
146145
userResponse.EnsureSuccessStatusCode();
147146
var text = await userResponse.Content.ReadAsStringAsync();
148-
JObject user = JObject.Parse(text);
147+
var user = JObject.Parse(text);
149148

150149
var identity = new ClaimsIdentity(
151150
context.Options.AuthenticationScheme,
@@ -184,7 +183,7 @@ dnx . web
184183
{
185184
signoutApp.Run(async context =>
186185
{
187-
string authType = context.Request.Query["authscheme"];
186+
var authType = context.Request.Query["authscheme"];
188187
if (!string.IsNullOrEmpty(authType))
189188
{
190189
// By default the client will be redirect back to the URL that issued the challenge (/login?authtype=foo),

src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public CookieAuthenticationMiddleware(
2222
[NotNull] IUrlEncoder urlEncoder,
2323
[NotNull] IOptions<CookieAuthenticationOptions> options,
2424
ConfigureOptions<CookieAuthenticationOptions> configureOptions)
25-
: base(next, options, loggerFactory, configureOptions)
25+
: base(next, options, loggerFactory, urlEncoder, configureOptions)
2626
{
2727
if (Options.Notifications == null)
2828
{

src/Microsoft.AspNet.Authentication.Cookies/CookieServiceCollectionExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public static IServiceCollection ConfigureCookieAuthentication([NotNull] this IS
2020

2121
public static IServiceCollection ConfigureCookieAuthentication([NotNull] this IServiceCollection services, [NotNull] Action<CookieAuthenticationOptions> configure, string optionsName)
2222
{
23-
services.AddWebEncoders();
2423
return services.Configure(configure, optionsName);
2524
}
2625

@@ -31,7 +30,6 @@ public static IServiceCollection ConfigureCookieAuthentication([NotNull] this IS
3130

3231
public static IServiceCollection ConfigureCookieAuthentication([NotNull] this IServiceCollection services, [NotNull] IConfiguration config, string optionsName)
3332
{
34-
services.AddWebEncoders();
3533
return services.Configure<CookieAuthenticationOptions>(config, optionsName);
3634
}
3735
}

src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHandler.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
using System.Security.Cryptography;
99
using System.Text;
1010
using System.Threading.Tasks;
11-
using Microsoft.AspNet.Http;
11+
using Microsoft.AspNet.Authentication.OAuth;
12+
using Microsoft.AspNet.Http.Authentication;
1213
using Microsoft.AspNet.Http.Collections;
1314
using Microsoft.AspNet.Http.Extensions;
14-
using Microsoft.AspNet.Http.Authentication;
15-
using Microsoft.AspNet.Authentication.OAuth;
1615
using Microsoft.AspNet.WebUtilities;
17-
using Microsoft.Framework.Logging;
16+
using Microsoft.Framework.WebEncoders;
1817
using Newtonsoft.Json.Linq;
1918

2019
namespace Microsoft.AspNet.Authentication.Facebook
@@ -53,7 +52,7 @@ protected override async Task<TokenResponse> ExchangeCodeAsync(string code, stri
5352

5453
protected override async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens)
5554
{
56-
var graphAddress = Options.UserInformationEndpoint + "?access_token=" + Uri.EscapeDataString(tokens.AccessToken);
55+
var graphAddress = Options.UserInformationEndpoint + "?access_token=" + UrlEncoder.UrlEncode(tokens.AccessToken);
5756
if (Options.SendAppSecretProof)
5857
{
5958
graphAddress += "&appsecret_proof=" + GenerateAppSecretProof(tokens.AccessToken);

src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.Framework.Internal;
1010
using Microsoft.Framework.Logging;
1111
using Microsoft.Framework.OptionsModel;
12+
using Microsoft.Framework.WebEncoders;
1213

1314
namespace Microsoft.AspNet.Authentication.Facebook
1415
{
@@ -28,10 +29,11 @@ public FacebookAuthenticationMiddleware(
2829
[NotNull] RequestDelegate next,
2930
[NotNull] IDataProtectionProvider dataProtectionProvider,
3031
[NotNull] ILoggerFactory loggerFactory,
32+
[NotNull] IUrlEncoder encoder,
3133
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
3234
[NotNull] IOptions<FacebookAuthenticationOptions> options,
3335
ConfigureOptions<FacebookAuthenticationOptions> configureOptions = null)
34-
: base(next, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
36+
: base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions)
3537
{
3638
if (string.IsNullOrWhiteSpace(Options.AppId))
3739
{

src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.Framework.Internal;
1010
using Microsoft.Framework.Logging;
1111
using Microsoft.Framework.OptionsModel;
12+
using Microsoft.Framework.WebEncoders;
1213

1314
namespace Microsoft.AspNet.Authentication.Google
1415
{
@@ -29,10 +30,11 @@ public GoogleAuthenticationMiddleware(
2930
[NotNull] RequestDelegate next,
3031
[NotNull] IDataProtectionProvider dataProtectionProvider,
3132
[NotNull] ILoggerFactory loggerFactory,
33+
[NotNull] IUrlEncoder encoder,
3234
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
3335
[NotNull] IOptions<GoogleAuthenticationOptions> options,
3436
ConfigureOptions<GoogleAuthenticationOptions> configureOptions = null)
35-
: base(next, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
37+
: base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions)
3638
{
3739
if (Options.Notifications == null)
3840
{

src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Framework.Internal;
88
using Microsoft.Framework.Logging;
99
using Microsoft.Framework.OptionsModel;
10+
using Microsoft.Framework.WebEncoders;
1011

1112
namespace Microsoft.AspNet.Authentication.MicrosoftAccount
1213
{
@@ -26,10 +27,11 @@ public MicrosoftAccountAuthenticationMiddleware(
2627
[NotNull] RequestDelegate next,
2728
[NotNull] IDataProtectionProvider dataProtectionProvider,
2829
[NotNull] ILoggerFactory loggerFactory,
30+
[NotNull] IUrlEncoder encoder,
2931
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
3032
[NotNull] IOptions<MicrosoftAccountAuthenticationOptions> options,
3133
ConfigureOptions<MicrosoftAccountAuthenticationOptions> configureOptions = null)
32-
: base(next, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
34+
: base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions)
3335
{
3436
if (Options.Notifications == null)
3537
{

src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Framework.Internal;
1212
using Microsoft.Framework.Logging;
1313
using Microsoft.Framework.OptionsModel;
14+
using Microsoft.Framework.WebEncoders;
1415

1516
namespace Microsoft.AspNet.Authentication.OAuth
1617
{
@@ -33,10 +34,11 @@ public OAuthAuthenticationMiddleware(
3334
[NotNull] RequestDelegate next,
3435
[NotNull] IDataProtectionProvider dataProtectionProvider,
3536
[NotNull] ILoggerFactory loggerFactory,
37+
[NotNull] IUrlEncoder encoder,
3638
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
3739
[NotNull] IOptions<TOptions> options,
3840
ConfigureOptions<TOptions> configureOptions = null)
39-
: base(next, options, loggerFactory, configureOptions)
41+
: base(next, options, loggerFactory, encoder, configureOptions)
4042
{
4143
// todo: review error handling
4244
if (string.IsNullOrWhiteSpace(Options.AuthenticationScheme))

src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Framework.Internal;
1212
using Microsoft.Framework.Logging;
1313
using Microsoft.Framework.OptionsModel;
14+
using Microsoft.Framework.WebEncoders;
1415
using Microsoft.IdentityModel.Protocols;
1516

1617
namespace Microsoft.AspNet.Authentication.OAuthBearer
@@ -32,9 +33,10 @@ public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAut
3233
public OAuthBearerAuthenticationMiddleware(
3334
[NotNull] RequestDelegate next,
3435
[NotNull] ILoggerFactory loggerFactory,
36+
[NotNull] IUrlEncoder encoder,
3537
[NotNull] IOptions<OAuthBearerAuthenticationOptions> options,
3638
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
37-
: base(next, options, loggerFactory, configureOptions)
39+
: base(next, options, loggerFactory, encoder, configureOptions)
3840
{
3941
if (Options.Notifications == null)
4042
{

src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ protected override async Task ApplyResponseChallengeAsync()
180180
ResponseMode = Options.ResponseMode,
181181
ResponseType = Options.ResponseType,
182182
Scope = Options.Scope,
183-
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Uri.EscapeDataString(Options.StateDataFormat.Protect(properties))
183+
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + UrlEncoder.UrlEncode(Options.StateDataFormat.Protect(properties))
184184
};
185185

186186
if (Options.ProtocolValidator.RequireNonce)

src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Microsoft.Framework.OptionsModel;
1818
using Microsoft.IdentityModel.Protocols;
1919
using Microsoft.Framework.Internal;
20+
using Microsoft.Framework.WebEncoders;
2021

2122
namespace Microsoft.AspNet.Authentication.OpenIdConnect
2223
{
@@ -40,10 +41,11 @@ public OpenIdConnectAuthenticationMiddleware(
4041
[NotNull] RequestDelegate next,
4142
[NotNull] IDataProtectionProvider dataProtectionProvider,
4243
[NotNull] ILoggerFactory loggerFactory,
44+
[NotNull] IUrlEncoder encoder,
4345
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
4446
[NotNull] IOptions<OpenIdConnectAuthenticationOptions> options,
4547
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions = null)
46-
: base(next, options, loggerFactory, configureOptions)
48+
: base(next, options, loggerFactory, encoder, configureOptions)
4749
{
4850
if (string.IsNullOrEmpty(Options.SignInScheme) && !string.IsNullOrEmpty(externalOptions.Options.SignInScheme))
4951
{

src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,17 @@ private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, str
244244
var parameterBuilder = new StringBuilder();
245245
foreach (var authorizationKey in authorizationParts)
246246
{
247-
parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value));
247+
parameterBuilder.AppendFormat("{0}={1}&", UrlEncoder.UrlEncode(authorizationKey.Key), UrlEncoder.UrlEncode(authorizationKey.Value));
248248
}
249249
parameterBuilder.Length--;
250250
var parameterString = parameterBuilder.ToString();
251251

252252
var canonicalizedRequestBuilder = new StringBuilder();
253253
canonicalizedRequestBuilder.Append(HttpMethod.Post.Method);
254254
canonicalizedRequestBuilder.Append("&");
255-
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint));
255+
canonicalizedRequestBuilder.Append(UrlEncoder.UrlEncode(RequestTokenEndpoint));
256256
canonicalizedRequestBuilder.Append("&");
257-
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString));
257+
canonicalizedRequestBuilder.Append(UrlEncoder.UrlEncode(parameterString));
258258

259259
var signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString());
260260
authorizationParts.Add("oauth_signature", signature);
@@ -264,7 +264,7 @@ private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, str
264264
foreach (var authorizationPart in authorizationParts)
265265
{
266266
authorizationHeaderBuilder.AppendFormat(
267-
"{0}=\"{1}\", ", authorizationPart.Key, Uri.EscapeDataString(authorizationPart.Value));
267+
"{0}=\"{1}\", ", authorizationPart.Key, UrlEncoder.UrlEncode(authorizationPart.Value));
268268
}
269269
authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2;
270270

@@ -306,17 +306,17 @@ private async Task<AccessToken> ObtainAccessTokenAsync(string consumerKey, strin
306306
var parameterBuilder = new StringBuilder();
307307
foreach (var authorizationKey in authorizationParts)
308308
{
309-
parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value));
309+
parameterBuilder.AppendFormat("{0}={1}&", UrlEncoder.UrlEncode(authorizationKey.Key), UrlEncoder.UrlEncode(authorizationKey.Value));
310310
}
311311
parameterBuilder.Length--;
312312
var parameterString = parameterBuilder.ToString();
313313

314314
var canonicalizedRequestBuilder = new StringBuilder();
315315
canonicalizedRequestBuilder.Append(HttpMethod.Post.Method);
316316
canonicalizedRequestBuilder.Append("&");
317-
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint));
317+
canonicalizedRequestBuilder.Append(UrlEncoder.UrlEncode(AccessTokenEndpoint));
318318
canonicalizedRequestBuilder.Append("&");
319-
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString));
319+
canonicalizedRequestBuilder.Append(UrlEncoder.UrlEncode(parameterString));
320320

321321
var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString());
322322
authorizationParts.Add("oauth_signature", signature);
@@ -327,7 +327,7 @@ private async Task<AccessToken> ObtainAccessTokenAsync(string consumerKey, strin
327327
foreach (var authorizationPart in authorizationParts)
328328
{
329329
authorizationHeaderBuilder.AppendFormat(
330-
"{0}=\"{1}\", ", authorizationPart.Key, Uri.EscapeDataString(authorizationPart.Value));
330+
"{0}=\"{1}\", ", authorizationPart.Key, UrlEncoder.UrlEncode(authorizationPart.Value));
331331
}
332332
authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2;
333333

@@ -367,15 +367,15 @@ private static string GenerateTimeStamp()
367367
return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture);
368368
}
369369

370-
private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData)
370+
private string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData)
371371
{
372372
using (var algorithm = new HMACSHA1())
373373
{
374374
algorithm.Key = Encoding.ASCII.GetBytes(
375375
string.Format(CultureInfo.InvariantCulture,
376376
"{0}&{1}",
377-
Uri.EscapeDataString(consumerSecret),
378-
string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
377+
UrlEncoder.UrlEncode(consumerSecret),
378+
string.IsNullOrEmpty(tokenSecret) ? string.Empty : UrlEncoder.UrlEncode(tokenSecret)));
379379
var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData));
380380
return Convert.ToBase64String(hash);
381381
}

src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.Framework.Internal;
1414
using Microsoft.Framework.Logging;
1515
using Microsoft.Framework.OptionsModel;
16+
using Microsoft.Framework.WebEncoders;
1617

1718
namespace Microsoft.AspNet.Authentication.Twitter
1819
{
@@ -36,10 +37,11 @@ public TwitterAuthenticationMiddleware(
3637
[NotNull] RequestDelegate next,
3738
[NotNull] IDataProtectionProvider dataProtectionProvider,
3839
[NotNull] ILoggerFactory loggerFactory,
40+
[NotNull] IUrlEncoder encoder,
3941
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
4042
[NotNull] IOptions<TwitterAuthenticationOptions> options,
4143
ConfigureOptions<TwitterAuthenticationOptions> configureOptions = null)
42-
: base(next, options, loggerFactory, configureOptions)
44+
: base(next, options, loggerFactory, encoder, configureOptions)
4345
{
4446
if (string.IsNullOrWhiteSpace(Options.ConsumerSecret))
4547
{

src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.AspNet.Http.Authentication;
1111
using Microsoft.Framework.Internal;
1212
using Microsoft.Framework.Logging;
13+
using Microsoft.Framework.WebEncoders;
1314

1415
namespace Microsoft.AspNet.Authentication
1516
{
@@ -50,6 +51,8 @@ protected HttpResponse Response
5051

5152
protected ILogger Logger { get; private set; }
5253

54+
protected IUrlEncoder UrlEncoder { get; private set; }
55+
5356
internal AuthenticationOptions BaseOptions
5457
{
5558
get { return _baseOptions; }
@@ -61,12 +64,13 @@ internal AuthenticationOptions BaseOptions
6164

6265
public bool Faulted { get; set; }
6366

64-
protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger)
67+
protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder)
6568
{
6669
_baseOptions = options;
6770
Context = context;
6871
RequestPathBase = Request.PathBase;
6972
Logger = logger;
73+
UrlEncoder = encoder;
7074

7175
RegisterAuthenticationHandler();
7276

0 commit comments

Comments
 (0)