Skip to content

Commit

Permalink
fix: Only allow legacy HTML on AditionalInfo content (#1210)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above -->

## Description

Added check for content type. 
Only add HTML to valid media types if scope is set, *and* type is
AdditionalInfo

## Related Issue(s)

- #1209

## Verification

- [x] **Your** code builds clean without any errors or warnings
- [ ] Manual testing done (required)
- [x] Relevant automated test added (if you find this hard, leave it and
we'll help out)

## Documentation

- [ ] Documentation is updated (either in `docs`-directory, Altinnpedia
or a separate linked PR in
[altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if
applicable)




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced media type validation based on user permissions for dialog
content.
- Introduced flexible initialization for test user claims in integration
tests.
- Added new test cases for dialog creation, specifically validating HTML
content handling.

- **Bug Fixes**
- Refined validation rules for media types and content values to ensure
proper checks.

- **Tests**
- Expanded integration tests to cover additional scenarios and edge
cases for dialog creation.

- **Chores**
- Improved dependency injection setup and initialization process for
integration tests.
- Updated local development configuration to disable various features
and enable authentication.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Magnus Sandgren <[email protected]>
  • Loading branch information
oskogstad and MagnusSandgren authored Oct 7, 2024
1 parent bddfa5e commit aa4acde
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal interface IIgnoreOnAssemblyScan;

internal sealed class ContentValueDtoValidator : AbstractValidator<ContentValueDto>, IIgnoreOnAssemblyScan
{
private const string LegacyHtmlMediaType = "text/html";
public const string LegacyHtmlMediaType = "text/html";

public ContentValueDtoValidator(DialogTransmissionContentType contentType)
{
Expand Down Expand Up @@ -75,6 +75,11 @@ private static string[] GetAllowedMediaTypes(DialogContentType contentType, IUse
return contentType.AllowedMediaTypes;
}

if (contentType.Id != DialogContentType.Values.AdditionalInfo)
{
return contentType.AllowedMediaTypes;
}

var allowHtmlSupport = user.GetPrincipal().HasScope(Constants.LegacyHtmlScope);

return allowHtmlSupport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class DialogApplication : IAsyncLifetime
private IMapper? _mapper;
private Respawner _respawner = null!;
private ServiceProvider _rootProvider = null!;
private ServiceProvider _fixtureRootProvider = null!;
private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder()
.WithImage("postgres:15.7")
.Build();
Expand All @@ -55,36 +56,58 @@ public async Task InitializeAsync()
return options;
});

_fixtureRootProvider = _rootProvider = BuildServiceCollection().BuildServiceProvider();

await _dbContainer.StartAsync();
await EnsureDatabaseAsync();
await BuildRespawnState();
}

/// <summary>
/// This method lets you configure the IoC container for an integration test.
/// It will be reset to the default configuration after each test.
/// You may only call this method once per test.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the method is called more than once per test.</exception>
public void ConfigureServiceCollection(Action<IServiceCollection> configure)
{
if (_rootProvider != _fixtureRootProvider)
{
throw new InvalidOperationException($"Only one call to {nameof(ConfigureServiceCollection)} is allowed per test.");
}

var serviceCollection = BuildServiceCollection();
configure(serviceCollection);
_rootProvider = serviceCollection.BuildServiceProvider();
}

private IServiceCollection BuildServiceCollection()
{
var serviceCollection = new ServiceCollection();

_rootProvider = serviceCollection
return serviceCollection
.AddApplication(Substitute.For<IConfiguration>(), Substitute.For<IHostEnvironment>())
.AddDistributedMemoryCache()
.AddLogging()
.AddTransient<ConvertDomainEventsToOutboxMessagesInterceptor>()
.AddDbContext<DialogDbContext>((services, options) =>
options.UseNpgsql(_dbContainer.GetConnectionString(), o =>
{
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
})
.AddInterceptors(services.GetRequiredService<ConvertDomainEventsToOutboxMessagesInterceptor>()))
{
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
})
.AddInterceptors(services.GetRequiredService<ConvertDomainEventsToOutboxMessagesInterceptor>()))
.AddScoped<IDialogDbContext>(x => x.GetRequiredService<DialogDbContext>())
.AddScoped<IUser, IntegrationTestUser>()
.AddScoped<IResourceRegistry, LocalDevelopmentResourceRegistry>()
.AddScoped<IServiceOwnerNameRegistry>(_ => CreateServiceOwnerNameRegistrySubstitute())
.AddScoped<IPartyNameRegistry>(_ => CreateNameRegistrySubstitute())
.AddScoped<IOptions<ApplicationSettings>>(_ => CreateApplicationSettingsSubstitute())
.AddScoped<ITopicEventSender>(_ => Substitute.For<ITopicEventSender>())
.AddScoped<IUnitOfWork, UnitOfWork>()
.AddScoped<IAltinnAuthorization, LocalDevelopmentAltinnAuthorization>()
.AddSingleton<IUser, IntegrationTestUser>()
.AddSingleton<ICloudEventBus, IntegrationTestCloudBus>()
.Decorate<IUserResourceRegistry, LocalDevelopmentUserResourceRegistryDecorator>()
.Decorate<IUserRegistry, LocalDevelopmentUserRegistryDecorator>()
.BuildServiceProvider();

await _dbContainer.StartAsync();
await EnsureDatabaseAsync();
await BuildRespawnState();
.Decorate<IUserRegistry, LocalDevelopmentUserRegistryDecorator>();
}

private static IPartyNameRegistry CreateNameRegistrySubstitute()
Expand Down Expand Up @@ -161,6 +184,7 @@ private static IServiceOwnerNameRegistry CreateServiceOwnerNameRegistrySubstitut
public async Task DisposeAsync()
{
await _rootProvider.DisposeAsync();
await _fixtureRootProvider.DisposeAsync();
await _dbContainer.DisposeAsync();
}

Expand All @@ -183,6 +207,11 @@ public async Task ResetState()
await using var connection = new NpgsqlConnection(_dbContainer.GetConnectionString());
await connection.OpenAsync();
await _respawner.ResetAsync(connection);
if (_rootProvider != _fixtureRootProvider)
{
await _rootProvider.DisposeAsync();
_rootProvider = _fixtureRootProvider;
}
}

private async Task EnsureDatabaseAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,36 @@ namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common;

internal sealed class IntegrationTestUser : IUser
{
private readonly ClaimsPrincipal _principal = new(new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, "Integration Test User"),
new Claim(ClaimTypes.NameIdentifier, "integration-test-user"),
new Claim("pid", "22834498646"),
new Claim("consumer",
"""
{
"authority": "iso6523-actorid-upis",
"ID": "0192:991825827"
}
""")
]));
public IntegrationTestUser(List<Claim> claims)
{
var defaultClaims = GetDefaultClaims();
defaultClaims.AddRange(claims);

_principal = new ClaimsPrincipal(new ClaimsIdentity(defaultClaims));
}
public IntegrationTestUser()
{
_principal = new ClaimsPrincipal(new ClaimsIdentity(GetDefaultClaims()));
}

private readonly ClaimsPrincipal _principal;

public ClaimsPrincipal GetPrincipal() => _principal;

private static List<Claim> GetDefaultClaims()
{
return
[
new Claim(ClaimTypes.Name, "Integration Test User"),
new Claim(ClaimTypes.NameIdentifier, "integration-test-user"),
new Claim("pid", "22834498646"),
new Claim("consumer",
"""
{
"authority": "iso6523-actorid-upis",
"ID": "0192:991825827"
}
""")
];
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;
using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content;
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Get;
using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common;
using Digdir.Tool.Dialogporten.GenerateFakeData;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using static Digdir.Domain.Dialogporten.Application.Integration.Tests.UuiDv7Utils;

namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.ServiceOwner.Dialogs.Commands;
Expand Down Expand Up @@ -270,4 +275,79 @@ public async Task Cannot_Create_Transmission_With_Empty_Content_Localization_Val
.Should()
.Be(2);
}

private const string LegacyHtmlMediaType = ContentValueDtoValidator.LegacyHtmlMediaType;

private static ContentValueDto CreateHtmlContentValueDto() => new()
{
MediaType = LegacyHtmlMediaType,
Value = [new() { LanguageCode = "nb", Value = "<p>Some HTML content</p>" }]
};

[Fact]
public async Task Cannot_Create_AdditionalInfo_Content_With_Html_MediaType_Without_Correct_Scope()
{
// Arrange
var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog();
createDialogCommand.Content.AdditionalInfo = CreateHtmlContentValueDto();

// Act
var response = await Application.Send(createDialogCommand);

// Assert
response.TryPickT2(out var validationError, out _).Should().BeTrue();
validationError.Should().NotBeNull();
validationError.Errors
.Count(e => e.AttemptedValue.Equals(LegacyHtmlMediaType))
.Should()
.Be(1);
}

[Fact]
public async Task Can_Create_AdditionalInfo_Content_With_Html_MediaType_With_Correct_Scope()
{
// Arrange
var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog();
createDialogCommand.Content.AdditionalInfo = CreateHtmlContentValueDto();

var userWithLegacyScope = new IntegrationTestUser([new("scope", Constants.LegacyHtmlScope)]);
Application.ConfigureServiceCollection(services =>
{
services.RemoveAll<IUser>();
services.AddSingleton<IUser>(userWithLegacyScope);
});

// Act
var response = await Application.Send(createDialogCommand);

// Assert
response.TryPickT0(out var success, out _).Should().BeTrue();
success.Should().NotBeNull();
}

[Fact]
public async Task Cannot_Create_Title_Content_With_Html_MediaType_With_Correct_Scope()
{
// Arrange
var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog();
createDialogCommand.Content.Title = CreateHtmlContentValueDto();

var userWithLegacyScope = new IntegrationTestUser([new("scope", Constants.LegacyHtmlScope)]);
Application.ConfigureServiceCollection(services =>
{
services.RemoveAll<IUser>();
services.AddSingleton<IUser>(userWithLegacyScope);
});

// Act
var response = await Application.Send(createDialogCommand);

// Assert
response.TryPickT2(out var validationError, out _).Should().BeTrue();
validationError.Should().NotBeNull();
validationError.Errors
.Count(e => e.AttemptedValue.Equals(LegacyHtmlMediaType))
.Should()
.Be(1);
}
}

0 comments on commit aa4acde

Please sign in to comment.