mirror of https://github.com/Squidex/squidex.git
16 changed files with 5 additions and 494 deletions
@ -1,59 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using LettuceEncrypt.Accounts; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.States; |
|
||||
|
|
||||
namespace Squidex.Domain.Users |
|
||||
{ |
|
||||
public sealed class DefaultCertificateAccountStore : IAccountStore |
|
||||
{ |
|
||||
private readonly ISnapshotStore<State, Guid> store; |
|
||||
|
|
||||
[CollectionName("Identity_CertificateAccount")] |
|
||||
public sealed class State |
|
||||
{ |
|
||||
public AccountModel Account { get; set; } |
|
||||
|
|
||||
public State() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public State(AccountModel account) |
|
||||
{ |
|
||||
Account = account; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public DefaultCertificateAccountStore(ISnapshotStore<State, Guid> store) |
|
||||
{ |
|
||||
Guard.NotNull(store, nameof(store)); |
|
||||
|
|
||||
this.store = store; |
|
||||
} |
|
||||
|
|
||||
public async Task<AccountModel?> GetAccountAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var (value, _) = await store.ReadAsync(default); |
|
||||
|
|
||||
return value?.Account; |
|
||||
} |
|
||||
|
|
||||
public Task SaveAccountAsync(AccountModel account, CancellationToken cancellationToken) |
|
||||
{ |
|
||||
Guard.NotNull(account, nameof(account)); |
|
||||
|
|
||||
var state = new State(account); |
|
||||
|
|
||||
return store.WriteAsync(default, state, EtagVersion.Any, 0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,73 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Security.Cryptography.X509Certificates; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using LettuceEncrypt; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.States; |
|
||||
|
|
||||
namespace Squidex.Domain.Users |
|
||||
{ |
|
||||
public sealed class DefaultCertificateStore : ICertificateRepository, ICertificateSource |
|
||||
{ |
|
||||
private readonly ISnapshotStore<State, Guid> store; |
|
||||
|
|
||||
[CollectionName("Identity_Certificates")] |
|
||||
public sealed class State |
|
||||
{ |
|
||||
public byte[] Certificate { get; set; } |
|
||||
|
|
||||
public State() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public State(X509Certificate2 certificate) |
|
||||
{ |
|
||||
Certificate = certificate.Export(X509ContentType.Pfx); |
|
||||
} |
|
||||
|
|
||||
public X509Certificate2 ToCertificate() |
|
||||
{ |
|
||||
return new X509Certificate2(Certificate); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public DefaultCertificateStore(ISnapshotStore<State, Guid> store) |
|
||||
{ |
|
||||
Guard.NotNull(store, nameof(store)); |
|
||||
|
|
||||
this.store = store; |
|
||||
} |
|
||||
|
|
||||
public async Task<IEnumerable<X509Certificate2>> GetCertificatesAsync(CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var result = new List<X509Certificate2>(); |
|
||||
|
|
||||
await store.ReadAllAsync((state, _) => |
|
||||
{ |
|
||||
result.Add(state.ToCertificate()); |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
}, cancellationToken); |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
public Task SaveAsync(X509Certificate2 certificate, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
Guard.NotNull(certificate, nameof(certificate)); |
|
||||
|
|
||||
var state = new State(certificate); |
|
||||
|
|
||||
return store.WriteAsync(Guid.NewGuid(), state, EtagVersion.Any, 0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,49 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Web.Pipeline |
|
||||
{ |
|
||||
public sealed class EnforceHttpsMiddleware : IMiddleware |
|
||||
{ |
|
||||
private readonly UrlsOptions urlsOptions; |
|
||||
|
|
||||
public EnforceHttpsMiddleware(IOptions<UrlsOptions> urlsOptions) |
|
||||
{ |
|
||||
Guard.NotNull(urlsOptions, nameof(urlsOptions)); |
|
||||
|
|
||||
this.urlsOptions = urlsOptions.Value; |
|
||||
} |
|
||||
|
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|
||||
{ |
|
||||
if (!urlsOptions.EnforceHTTPS) |
|
||||
{ |
|
||||
await next(context); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
var hostName = context.Request.Host.ToString().ToLowerInvariant(); |
|
||||
|
|
||||
if (!context.Request.IsHttps) |
|
||||
{ |
|
||||
var newUrl = string.Concat("https://", hostName, context.Request.Path, context.Request.QueryString); |
|
||||
|
|
||||
context.Response.Redirect(newUrl, true); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
await next(context); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,51 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using FakeItEasy; |
|
||||
using LettuceEncrypt.Accounts; |
|
||||
using Squidex.Infrastructure.States; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Users |
|
||||
{ |
|
||||
public class DefaultCertificateAccountStoreTests |
|
||||
{ |
|
||||
private readonly ISnapshotStore<DefaultCertificateAccountStore.State, Guid> store = A.Fake<ISnapshotStore<DefaultCertificateAccountStore.State, Guid>>(); |
|
||||
private readonly DefaultCertificateAccountStore sut; |
|
||||
|
|
||||
public DefaultCertificateAccountStoreTests() |
|
||||
{ |
|
||||
sut = new DefaultCertificateAccountStore(store); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_read_from_store() |
|
||||
{ |
|
||||
var model = new AccountModel(); |
|
||||
|
|
||||
A.CallTo(() => store.ReadAsync(default)) |
|
||||
.Returns((new DefaultCertificateAccountStore.State { Account = model }, 0)); |
|
||||
|
|
||||
var result = await sut.GetAccountAsync(default); |
|
||||
|
|
||||
Assert.Same(model, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_write_to_store() |
|
||||
{ |
|
||||
var model = new AccountModel(); |
|
||||
|
|
||||
await sut.SaveAccountAsync(model, default); |
|
||||
|
|
||||
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultCertificateAccountStore.State>._, A<long>._, 0)) |
|
||||
.MustHaveHappened(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Security.Cryptography; |
|
||||
using System.Security.Cryptography.X509Certificates; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using FakeItEasy; |
|
||||
using Squidex.Infrastructure.States; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Users |
|
||||
{ |
|
||||
public sealed class DefaultCertificateStoreTests |
|
||||
{ |
|
||||
private readonly ISnapshotStore<DefaultCertificateStore.State, Guid> store = A.Fake<ISnapshotStore<DefaultCertificateStore.State, Guid>>(); |
|
||||
private readonly DefaultCertificateStore sut; |
|
||||
|
|
||||
public DefaultCertificateStoreTests() |
|
||||
{ |
|
||||
sut = new DefaultCertificateStore(store); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_read_from_store() |
|
||||
{ |
|
||||
A.CallTo(() => store.ReadAllAsync(A<Func<DefaultCertificateStore.State, long, Task>>._, A<CancellationToken>._)) |
|
||||
.Invokes((Func<DefaultCertificateStore.State, long, Task> callback, CancellationToken _) => |
|
||||
{ |
|
||||
callback(new DefaultCertificateStore.State |
|
||||
{ |
|
||||
Certificate = MakeCert().Export(X509ContentType.Pfx) |
|
||||
}, 0); |
|
||||
|
|
||||
callback(new DefaultCertificateStore.State |
|
||||
{ |
|
||||
Certificate = MakeCert().Export(X509ContentType.Pfx) |
|
||||
}, 0); |
|
||||
}); |
|
||||
|
|
||||
var xml = await sut.GetCertificatesAsync(); |
|
||||
|
|
||||
Assert.Equal(2, xml.Count()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_write_to_store() |
|
||||
{ |
|
||||
var certificate = MakeCert(); |
|
||||
|
|
||||
await sut.SaveAsync(certificate, default); |
|
||||
|
|
||||
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultCertificateStore.State>._, A<long>._, 0)) |
|
||||
.MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
private static X509Certificate2 MakeCert() |
|
||||
{ |
|
||||
var ecdsa = ECDsa.Create(); |
|
||||
|
|
||||
var certificateRequest = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256); |
|
||||
|
|
||||
return certificateRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,85 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
using Xunit; |
|
||||
using Options = Microsoft.Extensions.Options.Options; |
|
||||
|
|
||||
namespace Squidex.Web.Pipeline |
|
||||
{ |
|
||||
public class EnforceHttpsMiddlewareTests |
|
||||
{ |
|
||||
private readonly RequestDelegate next; |
|
||||
private readonly UrlsOptions options = new UrlsOptions(); |
|
||||
private readonly EnforceHttpsMiddleware sut; |
|
||||
private bool isNextCalled; |
|
||||
|
|
||||
public EnforceHttpsMiddlewareTests() |
|
||||
{ |
|
||||
next = context => |
|
||||
{ |
|
||||
isNextCalled = true; |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
}; |
|
||||
|
|
||||
sut = new EnforceHttpsMiddleware(Options.Create(options)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_make_permanent_redirect_if_redirect_is_required() |
|
||||
{ |
|
||||
var httpContext = CreateHttpContext(); |
|
||||
|
|
||||
options.EnforceHTTPS = true; |
|
||||
|
|
||||
await sut.InvokeAsync(httpContext, next); |
|
||||
|
|
||||
Assert.False(isNextCalled); |
|
||||
Assert.Equal("https://squidex.local/path?query=1", httpContext.Response.Headers["Location"]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_not_redirect_if_already_on_https() |
|
||||
{ |
|
||||
var httpContext = CreateHttpContext("https"); |
|
||||
|
|
||||
options.EnforceHTTPS = true; |
|
||||
|
|
||||
await sut.InvokeAsync(httpContext, next); |
|
||||
|
|
||||
Assert.True(isNextCalled); |
|
||||
Assert.Null((string)httpContext.Response.Headers["Location"]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_not_redirect_if_not_required() |
|
||||
{ |
|
||||
var httpContext = CreateHttpContext(); |
|
||||
|
|
||||
options.EnforceHTTPS = false; |
|
||||
|
|
||||
await sut.InvokeAsync(httpContext, next); |
|
||||
|
|
||||
Assert.True(isNextCalled); |
|
||||
Assert.Null((string)httpContext.Response.Headers["Location"]); |
|
||||
} |
|
||||
|
|
||||
private static DefaultHttpContext CreateHttpContext(string scheme = "http") |
|
||||
{ |
|
||||
var httpContext = new DefaultHttpContext(); |
|
||||
|
|
||||
httpContext.Request.QueryString = new QueryString("?query=1"); |
|
||||
httpContext.Request.Host = new HostString("squidex.local"); |
|
||||
httpContext.Request.Path = new PathString("/path"); |
|
||||
httpContext.Request.Scheme = scheme; |
|
||||
|
|
||||
return httpContext; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue