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 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) catch (AmazonS3Exception ex) when (ex.StatusCode == HttpStatusCode.PreconditionFailed)
{ {
@ -237,11 +263,16 @@ namespace Squidex.Infrastructure.Assets
throw new AssetAlreadyExistsException(fileName); 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. try
request.InputStream = new SeekFakerStream(stream); {
request.AutoCloseStream = false; 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 System.Threading.Tasks;
using IdentityServer4.Extensions; using IdentityServer4.Extensions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Squidex.Web; using Squidex.Web;
namespace Squidex.Areas.Api.Config 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> /// </summary>
public RuleJobResult JobResult { get; set; } 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(); var result = new RuleEventDto();
SimpleMapper.Map(ruleEvent, result); SimpleMapper.Map(ruleEvent, result);
SimpleMapper.Map(ruleEvent.Job, 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) 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; 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> /// </summary>
public long Total { get; set; } 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 var result = new RuleEventsDto
{ {
Total = ruleEvents.Total, 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; 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 ruleEvents = await ruleEventsRepository.QueryByAppAsync(AppId, ruleId, skip, take);
var response = RuleEventsDto.FromRuleEvents(ruleEvents, this, app); var response = RuleEventsDto.FromRuleEvents(ruleEvents, Resources);
return Ok(response); return Ok(response);
} }

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

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

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

@ -55,8 +55,6 @@ namespace Squidex.Config
public bool AllowPasswordAuth { get; set; } public bool AllowPasswordAuth { get; set; }
public bool LocalApi { get; set; } = true;
public bool LockAutomatically { get; set; } public bool LockAutomatically { get; set; }
public bool NoConsent { 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) public static IApplicationBuilder UseSquidexTracking(this IApplicationBuilder app)
{ {
app.UseMiddleware<RequestExceptionMiddleware>(); app.UseMiddleware<RequestExceptionMiddleware>();
app.UseMiddleware<UsageMiddleware>();
app.UseMiddleware<RequestLogPerformanceMiddleware>(); app.UseMiddleware<RequestLogPerformanceMiddleware>();
app.UseMiddleware<UsageMiddleware>();
return app; return app;
} }

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

@ -7,6 +7,7 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@ -121,6 +122,20 @@ namespace Squidex.Infrastructure.Assets
Assert.Equal(assetLarge.ToArray(), readData.ToArray()); 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] [Fact]
public async Task Should_write_and_read_file_with_range() public async Task Should_write_and_read_file_with_range()
{ {
@ -215,5 +230,26 @@ namespace Squidex.Infrastructure.Assets
return memoryStream; 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