Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
9aef10f281 |
|||
|
e28959b068 |
|||
|
22c33c5388 |
7 changed files with 86 additions and 16 deletions
|
|
@ -9,12 +9,12 @@ include .$(APP_INSTALL_ENV).env
|
||||||
|
|
||||||
reinstall: copy-binaries
|
reinstall: copy-binaries
|
||||||
@for project in $(APP_PROJECT_NAMES); do \
|
@for project in $(APP_PROJECT_NAMES); do \
|
||||||
echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" \
|
echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" ; \
|
||||||
sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; done
|
sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; done
|
||||||
@echo "💯 backend started"
|
@echo "💯 backend started"
|
||||||
|
|
||||||
reinstall-prod:
|
reinstall-prod:
|
||||||
make APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production
|
make reinstall APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production
|
||||||
|
|
||||||
generated/$(APP_INSTALL_PREFIX)yavscOrg.service:
|
generated/$(APP_INSTALL_PREFIX)yavscOrg.service:
|
||||||
generated/$(APP_INSTALL_PREFIX)yavscBlogs.service:
|
generated/$(APP_INSTALL_PREFIX)yavscBlogs.service:
|
||||||
|
|
@ -82,14 +82,16 @@ install: copy-binaries copy-services
|
||||||
@sudo systemctl daemon-reload
|
@sudo systemctl daemon-reload
|
||||||
@for project in $(APP_PROJECT_NAMES); \
|
@for project in $(APP_PROJECT_NAMES); \
|
||||||
do \
|
do \
|
||||||
sudo systemctl enable yavsc$${project} ; \
|
echo "🖒 enable $(APP_INSTALL_PREFIX)yavsc$${project}" ; \
|
||||||
sudo systemctl start yavsc$${project} ; \
|
sudo systemctl enable $(APP_INSTALL_PREFIX)yavsc$${project} ; \
|
||||||
|
echo "🚀 start $(APP_INSTALL_PREFIX)yavsc$${project}" ; \
|
||||||
|
sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
stop-services:
|
stop-services:
|
||||||
@for project in $(APP_PROJECT_NAMES); \
|
@for project in $(APP_PROJECT_NAMES); \
|
||||||
do \
|
do \
|
||||||
|
echo "🔥 Stopping $(APP_INSTALL_PREFIX)yavsc$${project}" ; \
|
||||||
sudo systemctl stop $(APP_INSTALL_PREFIX)yavsc$${project} ; \
|
sudo systemctl stop $(APP_INSTALL_PREFIX)yavsc$${project} ; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,9 +209,12 @@ public sealed class EstimateApiControllerTests : IClassFixture<ApiWebServerFixtu
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seeds an estimate owned by alice (the provider) for the given
|
/// Seeds an estimate owned by alice (the provider) for the given
|
||||||
/// client, optionally already validated by the client.
|
/// client, optionally already validated by the client. By default the
|
||||||
|
/// estimate is also provider-validated (i.e. the provider has submitted
|
||||||
|
/// it); pass <paramref name="providerValidated"/> = false to seed a
|
||||||
|
/// freshly inserted request the provider hasn't worked on yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private long SeedEstimate(string clientId, bool clientValidated)
|
private long SeedEstimate(string clientId, bool clientValidated, bool providerValidated = true)
|
||||||
{
|
{
|
||||||
using var scope = _fixture.Services.CreateScope();
|
using var scope = _fixture.Services.CreateScope();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
@ -229,6 +232,7 @@ public sealed class EstimateApiControllerTests : IClassFixture<ApiWebServerFixtu
|
||||||
{
|
{
|
||||||
new() { Name = "Prestation", Description = "Prestation de base", Count = 1, UnitaryCost = 120m, Currency = "EUR" },
|
new() { Name = "Prestation", Description = "Prestation de base", Count = 1, UnitaryCost = 120m, Currency = "EUR" },
|
||||||
},
|
},
|
||||||
|
ProviderValidationDate = providerValidated ? DateTime.UtcNow : default,
|
||||||
ClientValidationDate = clientValidated ? DateTime.UtcNow : default,
|
ClientValidationDate = clientValidated ? DateTime.UtcNow : default,
|
||||||
};
|
};
|
||||||
db.Estimates.Add(estimate);
|
db.Estimates.Add(estimate);
|
||||||
|
|
@ -259,6 +263,26 @@ public sealed class EstimateApiControllerTests : IClassFixture<ApiWebServerFixtu
|
||||||
Assert.Equal("alice", item.GetProperty("ownerId").GetString());
|
Assert.Equal("alice", item.GetProperty("ownerId").GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOngoingEstimatesAsClient_excludes_inserted_requests_not_yet_provider_validated()
|
||||||
|
{
|
||||||
|
_fixture.ResetAndSeedActivityGraph();
|
||||||
|
var submittedId = SeedEstimate(clientId: "bob", clientValidated: false); // provider submitted: visible
|
||||||
|
SeedEstimate(clientId: "bob", clientValidated: false, providerValidated: false); // inserted, not worked on: excluded
|
||||||
|
|
||||||
|
using var http = NewClient("bob");
|
||||||
|
|
||||||
|
var response = await http.GetAsync("/api/v1/estimate/asclient", TestContext.Current.CancellationToken);
|
||||||
|
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}");
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(body);
|
||||||
|
var items = doc.RootElement.EnumerateArray().ToArray();
|
||||||
|
var item = Assert.Single(items);
|
||||||
|
Assert.Equal(submittedId, item.GetProperty("id").GetInt64());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetOngoingEstimatesAsProvider_returns_only_ongoing_estimates_established_by_the_provider()
|
public async Task GetOngoingEstimatesAsProvider_returns_only_ongoing_estimates_established_by_the_provider()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -201,12 +201,18 @@ namespace Yavsc.Controllers
|
||||||
// GET: api/estimate/asclient
|
// GET: api/estimate/asclient
|
||||||
// Ongoing estimates (not yet validated by the client) where the
|
// Ongoing estimates (not yet validated by the client) where the
|
||||||
// current user is the client who initiated the request.
|
// current user is the client who initiated the request.
|
||||||
|
// An estimate only becomes visible to the client once the provider
|
||||||
|
// has actually established and submitted it (ProviderValidationDate);
|
||||||
|
// a freshly inserted request the provider hasn't worked on yet is
|
||||||
|
// not something the client can validate, so it's excluded.
|
||||||
[HttpGet("asclient")]
|
[HttpGet("asclient")]
|
||||||
public IActionResult GetOngoingEstimatesAsClient()
|
public IActionResult GetOngoingEstimatesAsClient()
|
||||||
{
|
{
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
return Ok(_context.Estimates.Include(e => e.Bill)
|
return Ok(_context.Estimates.Include(e => e.Bill)
|
||||||
.Where(e => e.ClientId == uid && e.ClientValidationDate == default));
|
.Where(e => e.ClientId == uid
|
||||||
|
&& e.ProviderValidationDate != default
|
||||||
|
&& e.ClientValidationDate == default));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/estimate/asprovider
|
// GET: api/estimate/asprovider
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using IdentityServer8.Stores;
|
using IdentityServer8.Stores;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -40,4 +43,26 @@ public class AccountSmokeTests : IClassFixture<TestWebApplicationFactory>
|
||||||
|
|
||||||
Assert.Null(exception);
|
Assert.Null(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression: anonymous access to a restricted page used to redirect to
|
||||||
|
// the ASP.NET Identity default LoginPath "/Account/Login", which has no
|
||||||
|
// controller here. The application cookie must redirect to the real
|
||||||
|
// sign-in endpoint at /signin instead.
|
||||||
|
[Fact]
|
||||||
|
public async Task Anonymous_access_to_protected_page_redirects_to_signin()
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient(new WebApplicationFactoryClientOptions
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// EstimateController is [Authorize]; GET /Estimate hits Index().
|
||||||
|
var response = await client.GetAsync("/Estimate", TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
||||||
|
var location = response.Headers.Location;
|
||||||
|
Assert.NotNull(location);
|
||||||
|
Assert.Equal("/signin", location.AbsolutePath);
|
||||||
|
Assert.Contains("ReturnUrl=%2FEstimate", location.Query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,18 @@ public static class HostingExtensions
|
||||||
|
|
||||||
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
|
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
|
||||||
|
|
||||||
|
// The default application cookie LoginPath is "/Account/Login", which
|
||||||
|
// has no controller here — anonymous access to a restricted page must
|
||||||
|
// redirect to the actual sign-in endpoint instead. The paths mirror
|
||||||
|
// the route attributes on AccountController (Constants.SigninPath etc.,
|
||||||
|
// which carry a "~/" prefix that the cookie middleware does not want).
|
||||||
|
services.ConfigureApplicationCookie(options =>
|
||||||
|
{
|
||||||
|
options.LoginPath = "/signin";
|
||||||
|
options.LogoutPath = "/signout";
|
||||||
|
options.AccessDeniedPath = "/Account/AccessDenied";
|
||||||
|
});
|
||||||
|
|
||||||
// Dev-only: Chromium rejects SameSite=None without Secure on http://
|
// Dev-only: Chromium rejects SameSite=None without Secure on http://
|
||||||
// (e.g. http://localhost:5000). The default Identity cookie policy
|
// (e.g. http://localhost:5000). The default Identity cookie policy
|
||||||
// sets SameSite=None, which is invalid without Secure. Force Lax in
|
// sets SameSite=None, which is invalid without Secure. Force Lax in
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<markdown>
|
<markdown>
|
||||||
= La confidentialité
|
# La confidentialité
|
||||||
:hide-uri-scheme:
|
|
||||||
|
|
||||||
xref:Contact[Get Report]
|
À aucun moment, aucune adresse postale,
|
||||||
|
aucune adresse e-mail ni aucun numéro de téléphone
|
||||||
À aucun moment, aucune adresse postale, aucune adresse e-mail ni aucun numéro de téléphone
|
ne sont transmis à personne. Seul le système
|
||||||
ne sont transmis à personne. Seul le système et son link:Contact[possesseur] ont accès à ces informations.
|
et son link:Contact[possesseur] ont accès
|
||||||
|
à ces informations.
|
||||||
De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate].
|
|
||||||
|
|
||||||
|
De plus, le droit de retrait est permanent
|
||||||
|
et sa [mise en oeuvre est immédiate](/Account/Delete).
|
||||||
</markdown>
|
</markdown>
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ public static class ServiceExtensions
|
||||||
options.ClaimsIssuer = authority;
|
options.ClaimsIssuer = authority;
|
||||||
options.Audience = audiences[0];
|
options.Audience = audiences[0];
|
||||||
|
|
||||||
|
|
||||||
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
||||||
// Yavsc.Org itself) validates JWTs against the OP that runs
|
// Yavsc.Org itself) validates JWTs against the OP that runs
|
||||||
// on https://localhost:5001 with a self-signed dev cert.
|
// on https://localhost:5001 with a self-signed dev cert.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue