Browse Source

Support uploading streams without length for S3

pull/520/head
Sebastian 6 years ago
parent
commit
11c293b010
  1. 43
      backend/src/Squidex.Infrastructure.Amazon/Assets/AmazonS3AssetStore.cs
  2. 1
      backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs
  3. 12
      backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs
  4. 12
      backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs
  5. 2
      backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  6. 47
      backend/src/Squidex/Config/Authentication/IdentityServerServices.cs
  7. 2
      backend/src/Squidex/Config/MyIdentityOptions.cs
  8. 2
      backend/src/Squidex/Config/Web/WebExtensions.cs
  9. 36
      backend/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs

43
backend/src/Squidex.Infrastructure.Amazon/Assets/AmazonS3AssetStore.cs

@ -179,9 +179,35 @@ namespace Squidex.Infrastructure.Assets
Key = key
};
SetStream(stream, request);
if (!HasContentLength(stream))
{
var tempFileName = Path.GetTempFileName();
var tempStream = new FileStream(tempFileName,
FileMode.Create,
FileAccess.ReadWrite,
FileShare.Delete, 1024 * 16,
FileOptions.Asynchronous |
FileOptions.DeleteOnClose |
FileOptions.SequentialScan);
using (tempStream)
{
await stream.CopyToAsync(tempStream);
request.InputStream = tempStream;
await transferUtility.UploadAsync(request, ct);
}
}
else
{
request.InputStream = new SeekFakerStream(stream);
await transferUtility.UploadAsync(request, ct);
request.AutoCloseStream = false;
await transferUtility.UploadAsync(request, ct);
}
}
catch (AmazonS3Exception ex) when (ex.StatusCode == HttpStatusCode.PreconditionFailed)
{
@ -237,11 +263,16 @@ namespace Squidex.Infrastructure.Assets
throw new AssetAlreadyExistsException(fileName);
}
private static void SetStream(Stream stream, TransferUtilityUploadRequest request)
private static bool HasContentLength(Stream stream)
{
// Amazon S3 requires a seekable stream, but does not seek anything.
request.InputStream = new SeekFakerStream(stream);
request.AutoCloseStream = false;
try
{
return stream.Length > 0;
}
catch
{
return false;
}
}
}
}

1
backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks;
using IdentityServer4.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Squidex.Web;
namespace Squidex.Areas.Api.Config

12
backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs

@ -64,25 +64,25 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// </summary>
public RuleJobResult JobResult { get; set; }
public static RuleEventDto FromRuleEvent(IRuleEventEntity ruleEvent, ApiController controller, string app)
public static RuleEventDto FromRuleEvent(IRuleEventEntity ruleEvent, Resources resources)
{
var result = new RuleEventDto();
SimpleMapper.Map(ruleEvent, result);
SimpleMapper.Map(ruleEvent.Job, result);
return result.CreateLinks(controller, app);
return result.CreateLinks(resources);
}
private RuleEventDto CreateLinks(ApiController controller, string app)
private RuleEventDto CreateLinks(Resources resources)
{
var values = new { app, id = Id };
var values = new { app = resources.App, id = Id };
AddPutLink("update", controller.Url<RulesController>(x => nameof(x.PutEvent), values));
AddPutLink("update", resources.Url<RulesController>(x => nameof(x.PutEvent), values));
if (NextAttempt.HasValue)
{
AddDeleteLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteEvent), values));
AddDeleteLink("delete", resources.Url<RulesController>(x => nameof(x.DeleteEvent), values));
}
return this;

12
backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs

@ -26,20 +26,22 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// </summary>
public long Total { get; set; }
public static RuleEventsDto FromRuleEvents(IResultList<IRuleEventEntity> ruleEvents, ApiController controller, string app)
public static RuleEventsDto FromRuleEvents(IResultList<IRuleEventEntity> ruleEvents, Resources resources)
{
var result = new RuleEventsDto
{
Total = ruleEvents.Total,
Items = ruleEvents.Select(x => RuleEventDto.FromRuleEvent(x, controller, app)).ToArray()
Items = ruleEvents.Select(x => RuleEventDto.FromRuleEvent(x, resources)).ToArray()
};
return result.CreateLinks(controller, app);
return result.CreateLinks(resources);
}
private RuleEventsDto CreateLinks(ApiController controller, string app)
private RuleEventsDto CreateLinks(Resources resources)
{
AddSelfLink(controller.Url<RulesController>(x => nameof(x.GetEvents), new { app }));
var values = new { app = resources.App };
AddSelfLink(resources.Url<RulesController>(x => nameof(x.GetEvents), values));
return this;
}

2
backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -297,7 +297,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
{
var ruleEvents = await ruleEventsRepository.QueryByAppAsync(AppId, ruleId, skip, take);
var response = RuleEventsDto.FromRuleEvents(ruleEvents, this, app);
var response = RuleEventsDto.FromRuleEvents(ruleEvents, Resources);
return Ok(response);
}

47
backend/src/Squidex/Config/Authentication/IdentityServerServices.cs

@ -5,14 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using IdentityModel.AspNetCore.OAuth2Introspection;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
using Squidex.Web;
namespace Squidex.Config.Authentication
@ -21,41 +17,9 @@ namespace Squidex.Config.Authentication
{
public static AuthenticationBuilder AddSquidexIdentityServerAuthentication(this AuthenticationBuilder authBuilder, MyIdentityOptions identityOptions, IConfiguration config)
{
var apiScope = Constants.ApiScope;
var urlsOptions = config.GetSection("urls").Get<UrlsOptions>();
if (!string.IsNullOrWhiteSpace(urlsOptions.BaseUrl))
if (!string.IsNullOrWhiteSpace(identityOptions.AuthorityUrl))
{
string apiAuthorityUrl;
if (!string.IsNullOrWhiteSpace(identityOptions.AuthorityUrl))
{
apiAuthorityUrl = identityOptions.AuthorityUrl.BuildFullUrl(Constants.IdentityServerPrefix);
}
else
{
apiAuthorityUrl = urlsOptions.BuildUrl(Constants.IdentityServerPrefix);
}
if (identityOptions.LocalApi)
{
authBuilder.AddLocalApi(Constants.ApiSecurityScheme, options =>
{
options.ExpectedScope = apiScope;
});
}
else
{
authBuilder.AddIdentityServerAuthentication(Constants.ApiSecurityScheme, options =>
{
options.Authority = apiAuthorityUrl;
options.ApiName = apiScope;
options.ApiSecret = null;
options.RequireHttpsMetadata = identityOptions.RequiresHttps;
options.SupportedTokens = SupportedTokens.Jwt;
});
}
var apiAuthorityUrl = identityOptions.AuthorityUrl;
authBuilder.AddOpenIdConnect(options =>
{
@ -71,6 +35,13 @@ namespace Squidex.Config.Authentication
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
}
else
{
authBuilder.AddLocalApi(Constants.ApiSecurityScheme, options =>
{
options.ExpectedScope = Constants.ApiScope;
});
}
return authBuilder;
}

2
backend/src/Squidex/Config/MyIdentityOptions.cs

@ -55,8 +55,6 @@ namespace Squidex.Config
public bool AllowPasswordAuth { get; set; }
public bool LocalApi { get; set; } = true;
public bool LockAutomatically { get; set; }
public bool NoConsent { get; set; }

2
backend/src/Squidex/Config/Web/WebExtensions.cs

@ -36,8 +36,8 @@ namespace Squidex.Config.Web
public static IApplicationBuilder UseSquidexTracking(this IApplicationBuilder app)
{
app.UseMiddleware<RequestExceptionMiddleware>();
app.UseMiddleware<UsageMiddleware>();
app.UseMiddleware<RequestLogPerformanceMiddleware>();
app.UseMiddleware<UsageMiddleware>();
return app;
}

36
backend/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs

@ -7,6 +7,7 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Xunit;
@ -121,6 +122,20 @@ namespace Squidex.Infrastructure.Assets
Assert.Equal(assetLarge.ToArray(), readData.ToArray());
}
[Fact]
public async Task Should_upload_compressed_file()
{
var source = CreateDeflateStream(20_000);
await Sut.UploadAsync(fileName, source);
var readData = new MemoryStream();
await Sut.DownloadAsync(fileName, readData);
Assert.True(readData.Length > 0);
}
[Fact]
public async Task Should_write_and_read_file_with_range()
{
@ -215,5 +230,26 @@ namespace Squidex.Infrastructure.Assets
return memoryStream;
}
private static Stream CreateDeflateStream(int length)
{
var memoryStream = new MemoryStream();
using (var archive1 = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
using (var file = archive1.CreateEntry("test").Open())
{
var test = CreateFile(length);
test.CopyTo(file);
}
}
memoryStream.Position = 0;
var archive2 = new ZipArchive(memoryStream, ZipArchiveMode.Read);
return archive2.GetEntry("test").Open();
}
}
}

Loading…
Cancel
Save