Skip to content

Update Identity Components in Blazor project template #51134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relative
Microsoft.AspNetCore.Components.Rendering.ComponentState.LogicalParentComponentState.get -> Microsoft.AspNetCore.Components.Rendering.ComponentState?
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteData(System.Type! pageType, System.Collections.Generic.IReadOnlyDictionary<string!, object!>! routeValues) -> void
*REMOVED*Microsoft.AspNetCore.Components.RouteData.RouteValues.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>!
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode? renderMode) -> void
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,12 @@ public void AddComponentReferenceCapture(int sequence, Action<object> componentR
/// Adds a frame indicating the render mode on the enclosing component frame.
/// </summary>
/// <param name="renderMode">The <see cref="IComponentRenderMode"/>.</param>
public void AddComponentRenderMode(IComponentRenderMode renderMode)
public void AddComponentRenderMode(IComponentRenderMode? renderMode)
{
ArgumentNullException.ThrowIfNull(renderMode);
if (renderMode is null)
{
return;
}

// Note that a ComponentRenderMode frame is technically a child of the Component frame to which it applies,
// hence the terminology of "adding" it rather than "setting" it. For performance reasons, the diffing system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2141,18 +2141,26 @@ public void CannotAddComponentRenderModeToElement()
}

[Fact]
public void CannotAddNullComponentRenderMode()
public void CanAddNullComponentRenderMode()
{
// Arrange
var builder = new RenderTreeBuilder();

// Act
builder.OpenComponent<TestComponent>(0);
builder.AddComponentParameter(1, "param", 123);
builder.AddComponentRenderMode(null);
builder.CloseComponent();

// Act/Assert
var ex = Assert.Throws<ArgumentNullException>(() =>
{
builder.AddComponentRenderMode(null);
});
Assert.Equal("renderMode", ex.ParamName);
// Assert
Assert.Collection(
builder.GetFrames().AsEnumerable(),
frame =>
{
AssertFrame.Component<TestComponent>(frame, 2, 0);
Assert.False(frame.ComponentFrameFlags.HasFlag(ComponentFrameFlags.HasCallerSpecifiedRenderMode));
},
frame => AssertFrame.Attribute(frame, "param", 123, 1));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ internal void SetRequestContext(HttpContext context)
{
if (_context == null)
{
return null;
// We're in an interactive context. Use the token persisted during static rendering.
return base.GetAntiforgeryToken();
}

// We already have a callback setup to generate the token when the response starts if needed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public DefaultAntiforgeryStateProvider(PersistentComponentState state)
{
state.PersistAsJson(PersistenceKey, GetAntiforgeryToken());
return Task.CompletedTask;
}, RenderMode.InteractiveWebAssembly);
}, RenderMode.InteractiveAuto);

state.TryTakeFromJson(PersistenceKey, out _currentToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,19 @@ public void CanUseAntiforgeryTokenInWasm()
DispatchToFormCore(dispatchToForm);
}

[Fact]
public void CanUseAntiforgeryTokenWithServerInteractivity()
{
var dispatchToForm = new DispatchToForm(this)
{
Url = "forms/antiforgery-server-interactive",
FormCssSelector = "form",
InputFieldId = "value",
SuppressEnhancedNavigation = true,
};
DispatchToFormCore(dispatchToForm);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Components.TestServer.RazorComponents;
using Components.TestServer.RazorComponents.Pages.Forms;
using Components.TestServer.Services;
using Microsoft.AspNetCore.Components.WebAssembly.Server;
using Microsoft.AspNetCore.Mvc;

namespace TestServer;
Expand Down Expand Up @@ -155,9 +154,18 @@ private static void MapEnhancedNavigationEndpoints(IEndpointRouteBuilder endpoin
await response.WriteAsync("<html><body><h1>This is a non-Blazor endpoint</h1><p>That's all</p></body></html>");
});

endpoints.MapPost("api/antiforgery-form", ([FromForm] string value) =>
endpoints.MapPost("api/antiforgery-form", (
[FromForm] string value,
[FromForm(Name = "__RequestVerificationToken")] string? inFormCsrfToken,
[FromHeader(Name = "RequestVerificationToken")] string? inHeaderCsrfToken) =>
{
return Results.Ok(value);
// We shouldn't get this far without a valid CSRF token, but we'll double check it's there.
if (string.IsNullOrEmpty(inFormCsrfToken) && string.IsNullOrEmpty(inHeaderCsrfToken))
{
throw new InvalidOperationException("Invalid POST to api/antiforgery-form!");
}

return TypedResults.Text($"<p id='pass'>Hello {value}!</p>", "text/html");
});

endpoints.Map("/forms/endpoint-that-never-finishes-rendering", (HttpResponse response, CancellationToken token) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@page "/forms/antiforgery-server-interactive"

@using Microsoft.AspNetCore.Components.Forms

@rendermode RenderMode.InteractiveServer

<h3>FormRenderedWithServerInteractivityCanUseAntiforgeryToken</h3>

<form action="api/antiforgery-form" method="post">
<AntiforgeryToken />
<input type="text" id="value" name="value" />
@if (HttpContext is null)
{
<input id="send" type="submit" value="Send" />
}
</form>

@code {
[CascadingParameter]
HttpContext? HttpContext { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
{
@if (_succeeded)
{
<p id="pass">Posting the value succeded.</p>
<p id="pass">Posting the value succeeded.</p>
}
else
{
Expand All @@ -42,7 +42,7 @@ else
if (OperatingSystem.IsBrowser())
{
var antiforgery = AntiforgeryState.GetAntiforgeryToken();
_token = antiforgery.Value;
_token = antiforgery.Value;
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/ProjectTemplates/README-BASELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generating template-baselines.json

For small project template changes, you may be able to edit the `template-baselines.json` file manually. This is a good way to ensure you have correct expectations about the effects of your changes.

For larger changes such as adding entirely new templates, it may be impractical to type out the changes to `template-baselines.json` manually. In those cases you can follow a procedure like the following.

1. Ensure you've configured the necessary environment variables:
- `set PATH=c:\git\dotnet\aspnetcore\.dotnet\;%PATH%` (update path as needed)
- `set DOTNET_ROOT=c:\git\dotnet\aspnetcore\.dotnet` (update path as needed)
2. Get to a position where you can execute the modified template(s) locally, i.e.:
- Use `dotnet pack ProjectTemplatesNoDeps.slnf` (possibly with `--no-restore --no-dependencies`) to regenerate `Microsoft.DotNet.Web.ProjectTemplates.*.nupkg`
- Run one of the `scripts/*.ps1` scripts to install your template pack and execute your chosen template. For example, run `powershell .\scripts\Run-BlazorWeb-Locally.ps1`
- Once that has run, you should see your updated template listed when you execute `dotnet new list` or `dotnet new YourTemplateName --help`. At the point you can run `dotnet new YourTemplateName -o SomePath` directly if you want. However each time you edit template sources further, you will need to run `dotnet new uninstall Microsoft.DotNet.Web.ProjectTemplates.8.0` and then go back to the start of this whole step.
- Tip: the following command combines the above steps, to go directly from editing template sources to an updated local project output: `dotnet pack ProjectTemplatesNoDeps.slnf --no-restore --no-dependencies && dotnet new uninstall Microsoft.DotNet.Web.ProjectTemplates.8.0 && rm -rf scripts\MyBlazorApp && powershell .\scripts\Run-BlazorWeb-Locally.ps1`
3. After generating a particular project's output, the following can be run in a Bash prompt (e.g., using WSL):
- `cd src/ProjectTemplates/scripts`
- `export PROJECT_NAME=MyBlazorApp` (update as necessary - note this is the name of the directly under `scripts` containing your project output)
- `find $PROJECT_NAME -type f -not -path "*/obj/*" -not -path "*/bin/*" -not -path "*/.publish/*" | sed -e "s/^$PROJECT_NAME\///" | sed -e "s/$PROJECT_NAME/{ProjectName}/g" | sed 's/.*/ "&",/' | sort -f`
- This will emit the JSON-formatted lines you can manually insert into the relevant place inside `template-baselines.json`
2 changes: 1 addition & 1 deletion src/ProjectTemplates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Otherwise, you'll get a test error "Certificate error: Navigation blocked".

Then, use one of:

1. Run `src\ProjectTemplates\build.cmd -test -NoRestore -NoBuild -NoBuilddeps -configuration Release` (or equivalent src\ProjectTemplates\build.sh` command) to run all template tests.
1. Run `src\ProjectTemplates\build.cmd -test -NoRestore -NoBuild -NoBuildDeps -configuration Release` (or equivalent src\ProjectTemplates\build.sh` command) to run all template tests.
1. To test specific templates, use the `Run-[Template]-Locally.ps1` scripts in the script folder.
- These scripts do `dotnet new -i` with your packages, but also apply a series of fixes and tweaks to the created template which keep the fact that you don't have a production `Microsoft.AspNetCore.App` from interfering.
1. Run templates manually with `custom-hive` and `disable-sdk-templates` to install to a custom location and turn off the built-in templates e.g.
Expand Down
5 changes: 4 additions & 1 deletion src/ProjectTemplates/Shared/ArgConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ internal static class ArgConstants
public const string CalledApiScopes = "--called-api-scopes";
public const string CalledApiScopesUserReadWrite = $"{CalledApiScopes} user.readwrite";
public const string NoOpenApi = "--no-openapi";
public const string Auth = "-au";
public const string ClientId = "--client-id";
public const string Domain = "--domain";
public const string DefaultScope = "--default-scope";
Expand All @@ -26,5 +25,9 @@ internal static class ArgConstants
public const string NoHttps = "--no-https";
public const string PublishNativeAot = "--aot";
public const string NoInteractivity = "--interactivity none";
public const string WebAssemblyInteractivity = "--interactivity WebAssembly";
public const string AutoInteractivity = "--interactivity Auto";
public const string GlobalInteractivity = "--all-interactive";
public const string Empty = "--empty";
public const string IndividualAuth = "--auth Individual";
}
17 changes: 12 additions & 5 deletions src/ProjectTemplates/Shared/AspNetProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ public AspNetProcess(
_output = output;
_httpClient = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = true,
UseCookies = true,
CookieContainer = new CookieContainer(),
ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => (certificate.Subject != "CN=localhost" && errors == SslPolicyErrors.None) || certificate?.Thumbprint == _developmentCertificate.CertificateThumbprint,
})
{
Expand Down Expand Up @@ -124,6 +121,14 @@ public async Task AssertPagesOk(IEnumerable<Page> pages)
}
}

public async Task AssertPagesNotFound(IEnumerable<string> urls)
{
foreach (var url in urls)
{
await AssertNotFound(url);
}
}

public async Task ContainsLinks(Page page)
{
var response = await RetryHelper.RetryRequest(async () =>
Expand Down Expand Up @@ -290,8 +295,10 @@ public override string ToString()
}
}

public class Page
public class Page(string url)
{
public string Url { get; set; }
public Page() : this(null) { }

public string Url { get; set; } = url;
public IEnumerable<string> Links { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>DotNetEfFullPath</_Parameter1>
<_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfVersion)/tools/net6.0/any/dotnet-ef.dll</_Parameter2>
<_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfVersion)/tools/net8.0/any/dotnet-ef.dll</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>TestPackageRestorePath</_Parameter1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<!--#if (IndividualLocalAuth && !UseLocalDB) -->
<ItemGroup>
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
<None Update="Data\app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
</ItemGroup>

<!--#endif -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
{
"condition": "(!UseWebAssembly)",
"exclude": [
"BlazorWeb-CSharp/wwwroot/appsettings.Development.json",
"BlazorWeb-CSharp/wwwroot/appsettings.json",
"BlazorWeb-CSharp.Client/**",
"*.sln"
],
Expand All @@ -67,7 +65,8 @@
"condition": "(UseWebAssembly && InteractiveAtRoot)",
"rename": {
"BlazorWeb-CSharp/Components/Layout/": "./BlazorWeb-CSharp.Client/Layout/",
"BlazorWeb-CSharp/Components/Pages/": "./BlazorWeb-CSharp.Client/Pages/",
"BlazorWeb-CSharp/Components/Pages/Home.razor": "./BlazorWeb-CSharp.Client/Pages/Home.razor",
"BlazorWeb-CSharp/Components/Pages/Weather.razor": "./BlazorWeb-CSharp.Client/Pages/Weather.razor",
"BlazorWeb-CSharp/Components/Routes.razor": "./BlazorWeb-CSharp.Client/Routes.razor"
}
},
Expand Down Expand Up @@ -101,6 +100,7 @@
{
"condition": "(!SampleContent)",
"exclude": [
"BlazorWeb-CSharp/Components/Pages/Auth.*",
"BlazorWeb-CSharp/Components/Pages/Counter.*",
"BlazorWeb-CSharp/Components/Pages/Weather.*",
"BlazorWeb-CSharp/Components/Layout/NavMenu.*",
Expand All @@ -113,12 +113,8 @@
{
"condition": "(!IndividualLocalAuth)",
"exclude": [
"BlazorWeb-CSharp/Components/Identity/**",
"BlazorWeb-CSharp/Components/Layout/ManageLayout.razor",
"BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor",
"BlazorWeb-CSharp/Components/Pages/Account/**",
"BlazorWeb-CSharp/Components/Account/**",
"BlazorWeb-CSharp/Data/**",
"BlazorWeb-CSharp/Identity/**",
"BlazorWeb-CSharp.Client/PersistentAuthenticationStateProvider.cs",
"BlazorWeb-CSharp.Client/UserInfo.cs",
"BlazorWeb-CSharp.Client/Pages/Auth.razor"
Expand All @@ -127,7 +123,7 @@
{
"condition": "(!(IndividualLocalAuth && !UseLocalDB))",
"exclude": [
"BlazorWeb-CSharp/app.db"
"BlazorWeb-CSharp/Data/app.db"
]
},
{
Expand All @@ -139,19 +135,19 @@
{
"condition": "(!(IndividualLocalAuth && UseServer && UseWebAssembly))",
"exclude": [
"BlazorWeb-CSharp/Identity/PersistingRevalidatingAuthenticationStateProvider.cs"
"BlazorWeb-CSharp/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs"
]
},
{
"condition": "(!(IndividualLocalAuth && UseServer && !UseWebAssembly))",
"exclude": [
"BlazorWeb-CSharp/Identity/IdentityRevalidatingAuthenticationStateProvider.cs"
"BlazorWeb-CSharp/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs"
]
},
{
"condition": "(!(IndividualLocalAuth && !UseServer && UseWebAssembly))",
"exclude": [
"BlazorWeb-CSharp/Identity/PersistingServerAuthenticationStateProvider.cs"
"BlazorWeb-CSharp/Components/Account/PersistingServerAuthenticationStateProvider.cs"
]
},
{
Expand Down Expand Up @@ -189,6 +185,12 @@
"exclude": [
"BlazorWeb-CSharp/Data/SqlServer/**"
]
},
{
"condition": "(IndividualLocalAuth && UseWebAssembly)",
"rename": {
"BlazorWeb-CSharp/Components/Account/Shared/RedirectToLogin.razor": "BlazorWeb-CSharp.Client/RedirectToLogin.razor"
}
}
]
}
Expand Down Expand Up @@ -250,7 +252,7 @@
"sourceVariableName": "kestrelHttpPort",
"fallbackVariableName": "kestrelHttpPortGenerated"
},
"replaces": "5000"
"replaces": "5500"
},
"kestrelHttpsPort": {
"type": "parameter",
Expand All @@ -272,7 +274,7 @@
"sourceVariableName": "kestrelHttpsPort",
"fallbackVariableName": "kestrelHttpsPortGenerated"
},
"replaces": "5001"
"replaces": "5501"
},
"iisHttpPort": {
"type": "parameter",
Expand Down Expand Up @@ -349,7 +351,7 @@
"defaultValue": "InteractivePerPage",
"displayName": "_Interactivity location",
"description": "Chooses which components will have interactive rendering enabled",
"isEnabled": "(InteractivityPlatform != \"None\" && auth == \"None\")",
"isEnabled": "(InteractivityPlatform != \"None\")",
"choices": [
{
"choice": "InteractivePerPage",
Expand Down Expand Up @@ -413,7 +415,7 @@
"AllInteractive": {
"type": "parameter",
"datatype": "bool",
"isEnabled": "(InteractivityPlatform != \"None\" && auth == \"None\")",
"isEnabled": "(InteractivityPlatform != \"None\")",
"defaultValue": "false",
"displayName": "_Enable interactive rendering globally throughout the site",
"description": "Configures whether to make every page interactive by applying an interactive render mode at the top level. If false, pages will use static server rendering by default, and can be marked interactive on a per-page or per-component basis."
Expand Down
Loading