Update static files sample to use AuthZ middleware (#4279)
This commit is contained in:
parent
fef468aad1
commit
e310ccac7b
|
|
@ -5,32 +5,32 @@
|
||||||
<PropertyGroup Label="Package Versions">
|
<PropertyGroup Label="Package Versions">
|
||||||
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
|
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
|
||||||
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20181031.6</InternalAspNetCoreSdkPackageVersion>
|
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20181031.6</InternalAspNetCoreSdkPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>
|
<MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationGooglePackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreAuthenticationGooglePackageVersion>
|
<MicrosoftAspNetCoreAuthenticationGooglePackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreAuthenticationGooglePackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>
|
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>
|
<MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthorizationPackageVersion>3.0.0-a-alpha1-authz-middleware-16949</MicrosoftAspNetCoreAuthorizationPackageVersion>
|
<MicrosoftAspNetCoreAuthorizationPackageVersion>3.0.0-a-alpha1-authz-middleware-16949</MicrosoftAspNetCoreAuthorizationPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>3.0.0-a-alpha1-authz-middleware-16949</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
|
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>3.0.0-a-alpha1-authz-middleware-16949</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
|
||||||
<MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>
|
<MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>
|
||||||
<MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>
|
<MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>
|
<MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>
|
||||||
<MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
<MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreHostingPackageVersion>
|
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreHostingPackageVersion>
|
||||||
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreHttpPackageVersion>
|
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreHttpPackageVersion>
|
||||||
<MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>
|
<MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>
|
||||||
<MicrosoftAspNetCoreIdentityPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreIdentityPackageVersion>
|
<MicrosoftAspNetCoreIdentityPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreIdentityPackageVersion>
|
||||||
<MicrosoftAspNetCoreMvcPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreMvcPackageVersion>
|
<MicrosoftAspNetCoreMvcPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreMvcPackageVersion>
|
||||||
<MicrosoftAspNetCoreMvcTestingPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreMvcTestingPackageVersion>
|
<MicrosoftAspNetCoreMvcTestingPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreMvcTestingPackageVersion>
|
||||||
<MicrosoftAspNetCorePackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCorePackageVersion>
|
<MicrosoftAspNetCorePackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCorePackageVersion>
|
||||||
<MicrosoftAspNetCoreRewritePackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreRewritePackageVersion>
|
<MicrosoftAspNetCoreRewritePackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreRewritePackageVersion>
|
||||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
|
<MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
<MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||||
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10717</MicrosoftAspNetCoreTestHostPackageVersion>
|
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10772</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||||
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-preview-181106-14</MicrosoftAspNetCoreTestingPackageVersion>
|
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-preview-181106-14</MicrosoftAspNetCoreTestingPackageVersion>
|
||||||
<MicrosoftAspNetIdentityEntityFrameworkPackageVersion>2.2.1</MicrosoftAspNetIdentityEntityFrameworkPackageVersion>
|
<MicrosoftAspNetIdentityEntityFrameworkPackageVersion>2.2.1</MicrosoftAspNetIdentityEntityFrameworkPackageVersion>
|
||||||
<MicrosoftAzureKeyVaultPackageVersion>2.3.2</MicrosoftAzureKeyVaultPackageVersion>
|
<MicrosoftAzureKeyVaultPackageVersion>2.3.2</MicrosoftAzureKeyVaultPackageVersion>
|
||||||
|
|
@ -61,10 +61,10 @@
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
||||||
<MicrosoftNETCoreApp30PackageVersion>3.0.0-preview1-26907-05</MicrosoftNETCoreApp30PackageVersion>
|
<MicrosoftNETCoreApp30PackageVersion>3.0.0-preview1-26907-05</MicrosoftNETCoreApp30PackageVersion>
|
||||||
<MicrosoftNETSdkRazorPackageVersion>3.0.0-alpha1-10717</MicrosoftNETSdkRazorPackageVersion>
|
<MicrosoftNETSdkRazorPackageVersion>3.0.0-alpha1-10772</MicrosoftNETSdkRazorPackageVersion>
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||||
<MicrosoftOwinSecurityCookiesPackageVersion>3.0.1</MicrosoftOwinSecurityCookiesPackageVersion>
|
<MicrosoftOwinSecurityCookiesPackageVersion>3.0.1</MicrosoftOwinSecurityCookiesPackageVersion>
|
||||||
<MicrosoftOwinSecurityInteropPackageVersion>3.0.0-alpha1-10717</MicrosoftOwinSecurityInteropPackageVersion>
|
<MicrosoftOwinSecurityInteropPackageVersion>3.0.0-alpha1-10772</MicrosoftOwinSecurityInteropPackageVersion>
|
||||||
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
||||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||||
<SystemComponentModelAnnotationsPackageVersion>4.6.0-preview1-26907-04</SystemComponentModelAnnotationsPackageVersion>
|
<SystemComponentModelAnnotationsPackageVersion>4.6.0-preview1-26907-04</SystemComponentModelAnnotationsPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Endpoints;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -49,27 +50,23 @@ namespace StaticFilesAuth
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var userPath = Path.Combine(usersPath, userName);
|
if (context.Resource is Endpoint endpoint)
|
||||||
if (context.Resource is IFileInfo file)
|
|
||||||
{
|
{
|
||||||
var path = Path.GetDirectoryName(file.PhysicalPath);
|
var userPath = Path.Combine(usersPath, userName);
|
||||||
return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase)
|
var directory = endpoint.Metadata.GetMetadata<DirectoryInfo>();
|
||||||
|| string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase)
|
if (directory != null)
|
||||||
|| path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
}
|
return string.Equals(directory.FullName, basePath, StringComparison.OrdinalIgnoreCase)
|
||||||
else if (context.Resource is IDirectoryContents dir)
|
|| string.Equals(directory.FullName, usersPath, StringComparison.OrdinalIgnoreCase)
|
||||||
{
|
|| string.Equals(directory.FullName, userPath, StringComparison.OrdinalIgnoreCase)
|
||||||
// https://github.com/aspnet/Home/issues/3073
|
|| directory.FullName.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
|
||||||
// This won't work right if the directory is empty
|
}
|
||||||
var path = Path.GetDirectoryName(dir.First().PhysicalPath);
|
|
||||||
return string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase)
|
throw new InvalidOperationException($"Missing file system metadata.");
|
||||||
|| string.Equals(path, usersPath, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(path, userPath, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| path.StartsWith(userPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException($"Unknown resource type '{context.Resource.GetType()}'");
|
throw new InvalidOperationException($"Unknown resource type '{context.Resource.GetType()}'");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -98,12 +95,15 @@ namespace StaticFilesAuth
|
||||||
|
|
||||||
app.Map("/MapAuthenticatedFiles", branch =>
|
app.Map("/MapAuthenticatedFiles", branch =>
|
||||||
{
|
{
|
||||||
MapAuthenticatedFiles(branch, files);
|
branch.Use((context, next) => { SetFileEndpoint(context, files, null); return next(); });
|
||||||
|
branch.UseAuthorization();
|
||||||
|
SetupFileServer(branch, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.Map("/MapImperativeFiles", branch =>
|
app.Map("/MapImperativeFiles", branch =>
|
||||||
{
|
{
|
||||||
MapImperativeFiles(authorizationService, branch, files);
|
branch.Use((context, next) => { SetFileEndpoint(context, files, "files"); return next(); });
|
||||||
|
branch.UseAuthorization();
|
||||||
|
SetupFileServer(branch, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseMvc(routes =>
|
app.UseMvc(routes =>
|
||||||
|
|
@ -114,81 +114,51 @@ namespace StaticFilesAuth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blanket authorization, any authenticated user is allowed access to these resources.
|
private void SetupFileServer(IApplicationBuilder builder, IFileProvider files)
|
||||||
private static void MapAuthenticatedFiles(IApplicationBuilder branch, PhysicalFileProvider files)
|
|
||||||
{
|
{
|
||||||
branch.Use(async (context, next) =>
|
builder.UseFileServer(new FileServerOptions()
|
||||||
{
|
|
||||||
if (!context.User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
await context.ChallengeAsync(new AuthenticationProperties()
|
|
||||||
{
|
|
||||||
// https://github.com/aspnet/Security/issues/1730
|
|
||||||
// Return here after authenticating
|
|
||||||
RedirectUri = context.Request.PathBase + context.Request.Path + context.Request.QueryString
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
branch.UseFileServer(new FileServerOptions()
|
|
||||||
{
|
{
|
||||||
EnableDirectoryBrowsing = true,
|
EnableDirectoryBrowsing = true,
|
||||||
FileProvider = files
|
FileProvider = files
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Policy based authorization, requests must meet the policy criteria to be get access to the resources.
|
private static void SetFileEndpoint(HttpContext context, PhysicalFileProvider files, string policy)
|
||||||
private static void MapImperativeFiles(IAuthorizationService authorizationService, IApplicationBuilder branch, PhysicalFileProvider files)
|
|
||||||
{
|
{
|
||||||
branch.Use(async (context, next) =>
|
var fileSystemPath = GetFileSystemPath(files, context.Request.Path);
|
||||||
|
if (fileSystemPath != null)
|
||||||
{
|
{
|
||||||
var fileInfo = files.GetFileInfo(context.Request.Path);
|
var metadata = new List<object>();
|
||||||
AuthorizationResult result = null;
|
metadata.Add(new DirectoryInfo(Path.GetDirectoryName(fileSystemPath)));
|
||||||
if (fileInfo.Exists)
|
metadata.Add(new AuthorizeAttribute(policy));
|
||||||
{
|
|
||||||
result = await authorizationService.AuthorizeAsync(context.User, fileInfo, "files");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// https://github.com/aspnet/Home/issues/2537
|
|
||||||
var dir = files.GetDirectoryContents(context.Request.Path);
|
|
||||||
if (dir.Exists)
|
|
||||||
{
|
|
||||||
result = await authorizationService.AuthorizeAsync(context.User, dir, "files");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = StatusCodes.Status404NotFound;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.Succeeded)
|
var endpoint = new Endpoint(
|
||||||
{
|
c => throw new InvalidOperationException("Static file middleware should return file request."),
|
||||||
if (!context.User.Identity.IsAuthenticated)
|
new EndpointMetadataCollection(metadata),
|
||||||
{
|
context.Request.Path);
|
||||||
await context.ChallengeAsync(new AuthenticationProperties()
|
|
||||||
{
|
|
||||||
// https://github.com/aspnet/Security/issues/1730
|
|
||||||
// Return here after authenticating
|
|
||||||
RedirectUri = context.Request.PathBase + context.Request.Path + context.Request.QueryString
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Authenticated but not authorized
|
|
||||||
await context.ForbidAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
context.SetEndpoint(endpoint);
|
||||||
});
|
}
|
||||||
branch.UseFileServer(new FileServerOptions()
|
}
|
||||||
|
|
||||||
|
private static string GetFileSystemPath(PhysicalFileProvider files, string path)
|
||||||
|
{
|
||||||
|
var fileInfo = files.GetFileInfo(path);
|
||||||
|
if (fileInfo.Exists)
|
||||||
{
|
{
|
||||||
EnableDirectoryBrowsing = true,
|
return Path.Join(files.Root, path);
|
||||||
FileProvider = files
|
}
|
||||||
});
|
else
|
||||||
|
{
|
||||||
|
// https://github.com/aspnet/Home/issues/2537
|
||||||
|
var dir = files.GetDirectoryContents(path);
|
||||||
|
if (dir.Exists)
|
||||||
|
{
|
||||||
|
return Path.Join(files.Root, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue