Skip to content

Commit

Permalink
Merge branch 'main' into 13764-support-link-a-sub-form-to-data-model
Browse files Browse the repository at this point in the history
  • Loading branch information
lassopicasso committed Nov 22, 2024
2 parents b5a57c4 + 84df844 commit d54007e
Show file tree
Hide file tree
Showing 164 changed files with 2,709 additions and 1,791 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- uses: actions/checkout@v4

- name: Install dotnet ef # Version should be the same as Migrations docker file and project
run: dotnet tool install --version 8.0.7 --global dotnet-ef
run: dotnet tool install --version 9.0.0 --global dotnet-ef

- name: Check if migrations script can be generated
run: |
Expand Down
2 changes: 1 addition & 1 deletion backend/Migrations.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /app

COPY . .

RUN dotnet tool install --version 8.0.7 --global dotnet-ef
RUN dotnet tool install --version 9.0.0 --global dotnet-ef
ENV PATH="$PATH:/root/.dotnet/tools"

ENV OidcLoginSettings__FetchClientIdAndSecretFromRootEnvFile=false
Expand Down
4 changes: 2 additions & 2 deletions backend/packagegroups/NuGet.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
<PackageReference Update="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Update="HtmlAgilityPack" Version="1.11.67" />
<PackageReference Update="Microsoft.DiaSymReader.Native" Version="1.7.0" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
<PackageReference Update="Microsoft.FeatureManagement.AspNetCore" Version="3.5.0" />
<PackageReference Update="Scrutor" Version="4.2.2" />
<PackageReference Update="Polly" Version="8.4.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void ParseFieldProperty(ElementMetadata element, StringBuilder classBuil
else
{
elementOrder += 1;
AddXmlElementAnnotation(element, classBuilder, elementOrder);
AddXmlElementAnnotation(element, classBuilder, elementOrder, !isValueType && (element.Nillable ?? false));

// Temporary fix - as long as we use System.Text.Json for serialization and Newtonsoft.Json for
// deserialization, we need both JsonProperty and JsonPropertyName annotations.
Expand Down Expand Up @@ -265,16 +265,21 @@ private void ParseGroupProperty(ElementMetadata element, StringBuilder classBuil
}
}

private void AddXmlElementAnnotation(ElementMetadata element, StringBuilder classBuilder, int elementOrder)
private void AddXmlElementAnnotation(ElementMetadata element, StringBuilder classBuilder, int elementOrder, bool addNillableAttribute = false)
{
if (element.OrderOblivious)
string additionalAttributeParams = string.Empty;
if (!element.OrderOblivious)
{
classBuilder.AppendLine($"""{Indent(2)}[XmlElement("{element.XName}")]""");
additionalAttributeParams += $", Order = {elementOrder}";
}
else

if (addNillableAttribute)
{
classBuilder.AppendLine($"""{Indent(2)}[XmlElement("{element.XName}", Order = {elementOrder})]""");
additionalAttributeParams += ", IsNullable = true";
}


classBuilder.AppendLine($"""{Indent(2)}[XmlElement("{element.XName}"{additionalAttributeParams})]""");
}

private void AddShouldSerializeForTagContent(ElementMetadata element, StringBuilder classBuilder, ModelMetadata modelMetadata)
Expand Down
22 changes: 20 additions & 2 deletions backend/src/Designer/Controllers/ResourceAdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public async Task<ActionResult> ImportResource(string org, string serviceCode, i
public async Task<ActionResult> GetDelegationCount(string org, string serviceCode, int serviceEdition, string env)
{
ServiceResource resource = await _resourceRegistry.GetServiceResourceFromService(serviceCode, serviceEdition, env.ToLower());
if (resource?.HasCompetentAuthority == null || !resource.HasCompetentAuthority.Orgcode.Equals(org, StringComparison.InvariantCultureIgnoreCase))
if (!IsServiceOwner(resource, org))
{
return new UnauthorizedResult();
}
Expand All @@ -348,7 +348,7 @@ public async Task<ActionResult> GetDelegationCount(string org, string serviceCod
public async Task<ActionResult> MigrateDelegations([FromBody] ExportDelegationsRequestBE delegationRequest, string org, string env)
{
ServiceResource resource = await _resourceRegistry.GetServiceResourceFromService(delegationRequest.ServiceCode, delegationRequest.ServiceEditionCode, env.ToLower());
if (resource?.HasCompetentAuthority == null || !resource.HasCompetentAuthority.Orgcode.Equals(org, StringComparison.InvariantCultureIgnoreCase))
if (!IsServiceOwner(resource, org))
{
return new UnauthorizedResult();
}
Expand Down Expand Up @@ -602,6 +602,24 @@ private async Task<OrgList> GetOrgList()
return orgList;
}

private static bool IsServiceOwner(ServiceResource? resource, string loggedInOrg)
{
if (resource?.HasCompetentAuthority == null)
{
return false;
}

bool isOwnedByOrg = resource.HasCompetentAuthority.Orgcode.Equals(loggedInOrg, StringComparison.InvariantCultureIgnoreCase);

if (OrgUtil.IsTestEnv(loggedInOrg))
{
return isOwnedByOrg || resource.HasCompetentAuthority.Orgcode.Equals("acn", StringComparison.InvariantCultureIgnoreCase);
}

return isOwnedByOrg;

}

private async Task<ResourceVersionInfo> AddEnvironmentResourceStatus(string env, string id)
{
ServiceResource resource = await _resourceRegistry.GetResource(id, env);
Expand Down
6 changes: 0 additions & 6 deletions backend/src/Designer/Designer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
<Configurations>Release;Debug</Configurations>
</PropertyGroup>

<PropertyGroup>
<CodeAnalisysCompatibleVersion>4.5.0</CodeAnalisysCompatibleVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Altinn.App.Core" />
<PackageReference Include="Altinn.Authorization.ABAC" />
Expand Down Expand Up @@ -59,8 +55,6 @@
<PackageReference Include="NuGet.Versioning" />
<PackageReference Include="Polly" />
<PackageReference Include="Scrutor" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(CodeAnalisysCompatibleVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="$(CodeAnalisysCompatibleVersion)" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Altinn.Studio.Designer.Exceptions.Options;

/// <summary>
/// Indicates that an error occurred during json serialization of options.
/// </summary>
[Serializable]
public class InvalidOptionsFormatException : Exception
{
/// <inheritdoc/>
public InvalidOptionsFormatException()
{
}

/// <inheritdoc/>
public InvalidOptionsFormatException(string message) : base(message)
{
}

/// <inheritdoc/>
public InvalidOptionsFormatException(string message, Exception innerException) : base(message, innerException)
{
}
}
6 changes: 6 additions & 0 deletions backend/src/Designer/Filters/Options/OptionsErrorCodes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Altinn.Studio.Designer.Filters.Options;

public class OptionsErrorCodes
{
public const string InvalidOptionsFormat = nameof(InvalidOptionsFormat);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Net;
using Altinn.Studio.Designer.Exceptions.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Altinn.Studio.Designer.Filters.Options;

public class OptionsExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);

if (context.ActionDescriptor is not ControllerActionDescriptor)
{
return;
}

if (context.Exception is InvalidOptionsFormatException)
{
context.Result = new ObjectResult(ProblemDetailsUtils.GenerateProblemDetails(context.Exception, OptionsErrorCodes.InvalidOptionsFormat, HttpStatusCode.BadRequest)) { StatusCode = (int)HttpStatusCode.BadRequest };
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ namespace Altinn.Studio.Designer.Filters;
public static class ProblemDetailsExtensionsCodes
{
public const string ErrorCode = "errorCode";

public const string Detail = "detail";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;

namespace Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

public class NotNullableAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
return new ValidationResult("The field is required.");
}
return ValidationResult.Success;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Altinn.Studio.Designer.Exceptions.Options;

namespace Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

public class OptionConverter : JsonConverter<object>
public class OptionValueConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Expand All @@ -14,7 +15,7 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS
JsonTokenType.Number when reader.TryGetDouble(out double d) => d,
JsonTokenType.True => true,
JsonTokenType.False => false,
_ => throw new JsonException($"Unsupported JSON token for Option.Value: {reader.TokenType}")
_ => throw new InvalidOptionsFormatException($"Unsupported JSON token for Value field, {typeToConvert}: {reader.TokenType}.")
};
}

Expand All @@ -32,7 +33,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp
writer.WriteBooleanValue(b);
break;
default:
throw new JsonException("Unsupported type for Option.Value.");
throw new InvalidOptionsFormatException($"{value} is an unsupported type for Value field. Accepted types are string, double and bool.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -425,20 +425,9 @@ public string[] GetLayoutSetNames()
public void ChangeLayoutSetFolderName(string oldLayoutSetName, string newLayoutSetName, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (DirectoryExistsByRelativePath(GetPathToLayoutSet(newLayoutSetName)))
{
throw new NonUniqueLayoutSetIdException("Suggested new layout set name already exist");
}
string destAbsolutePath = GetAbsoluteFileOrDirectoryPathSanitized(GetPathToLayoutSet(newLayoutSetName, true));

string sourceRelativePath = GetPathToLayoutSet(oldLayoutSetName, true);
if (!DirectoryExistsByRelativePath(sourceRelativePath))
{
throw new NotFoundException("Layout set you are trying to change doesn't exist");
}

string sourceAbsolutePath = GetAbsoluteFileOrDirectoryPathSanitized(sourceRelativePath);
Directory.Move(sourceAbsolutePath, destAbsolutePath);
string currentDirectoryPath = GetPathToLayoutSet(oldLayoutSetName, true);
string newDirectoryPath = GetPathToLayoutSet(newLayoutSetName, true);
MoveDirectoryByRelativePath(currentDirectoryPath, newDirectoryPath);
}

/// <summary>
Expand Down Expand Up @@ -575,15 +564,7 @@ public void UpdateFormLayoutName(string layoutSetName, string layoutFileName, st
{
string currentFilePath = GetPathToLayoutFile(layoutSetName, layoutFileName);
string newFilePath = GetPathToLayoutFile(layoutSetName, newFileName);
if (!FileExistsByRelativePath(currentFilePath))
{
throw new FileNotFoundException("Layout does not exist.");
}
if (FileExistsByRelativePath(newFilePath))
{
throw new ArgumentException("New layout name must be unique.");
}
File.Move(GetAbsoluteFileOrDirectoryPathSanitized(currentFilePath), GetAbsoluteFileOrDirectoryPathSanitized(newFilePath));
MoveFileByRelativePath(currentFilePath, newFilePath, newFileName);
}

public async Task<LayoutSets> GetLayoutSetsFile(CancellationToken cancellationToken = default)
Expand Down
45 changes: 36 additions & 9 deletions backend/src/Designer/Infrastructure/GitRepository/GitRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,19 +254,46 @@ public void DeleteFileByAbsolutePath(string absoluteFilePath)
/// <param name="destinationFileName">FileName for the destination file</param>
protected void MoveFileByRelativePath(string sourceRelativeFilePath, string destRelativeFilePath, string destinationFileName)
{
if (FileExistsByRelativePath(sourceRelativeFilePath))
if (!FileExistsByRelativePath(sourceRelativeFilePath))
{
Guard.AssertNotNullOrEmpty(sourceRelativeFilePath, nameof(sourceRelativeFilePath));
throw new FileNotFoundException($"File {sourceRelativeFilePath} does not exist.");
}

string sourceAbsoluteFilePath = GetAbsoluteFileOrDirectoryPathSanitized(sourceRelativeFilePath);
string destAbsoluteFilePath = GetAbsoluteFileOrDirectoryPathSanitized(destRelativeFilePath);
string destAbsoluteParentDirPath = destAbsoluteFilePath.Remove(destAbsoluteFilePath.IndexOf(destinationFileName, StringComparison.Ordinal));
Directory.CreateDirectory(destAbsoluteParentDirPath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, sourceAbsoluteFilePath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, destAbsoluteFilePath);
if (FileExistsByRelativePath(destRelativeFilePath))
{
throw new IOException($"Suggested file name {destinationFileName} already exists.");
}
string sourceAbsoluteFilePath = GetAbsoluteFileOrDirectoryPathSanitized(sourceRelativeFilePath);
string destAbsoluteFilePath = GetAbsoluteFileOrDirectoryPathSanitized(destRelativeFilePath);
string destAbsoluteParentDirPath = destAbsoluteFilePath.Remove(destAbsoluteFilePath.IndexOf(destinationFileName, StringComparison.Ordinal));
Directory.CreateDirectory(destAbsoluteParentDirPath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, sourceAbsoluteFilePath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, destAbsoluteFilePath);

File.Move(sourceAbsoluteFilePath, destAbsoluteFilePath);
}

File.Move(sourceAbsoluteFilePath, destAbsoluteFilePath);
/// <summary>
/// Move the specified folder to specified destination
/// </summary>
/// <param name="sourceRelativeDirectoryPath">Relative path to folder to be moved.</param>
/// <param name="destRelativeDirectoryPath">Relative path to destination of moved folder.</param>
protected void MoveDirectoryByRelativePath(string sourceRelativeDirectoryPath, string destRelativeDirectoryPath)
{
if (!DirectoryExistsByRelativePath(sourceRelativeDirectoryPath))
{
throw new DirectoryNotFoundException($"Directory {sourceRelativeDirectoryPath} does not exist.");
}
if (DirectoryExistsByRelativePath(destRelativeDirectoryPath))
{
throw new IOException($"Suggested directory {destRelativeDirectoryPath} already exists.");
}
string sourceAbsoluteDirectoryPath = GetAbsoluteFileOrDirectoryPathSanitized(sourceRelativeDirectoryPath);
string destAbsoluteDirectoryPath = GetAbsoluteFileOrDirectoryPathSanitized(destRelativeDirectoryPath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, sourceAbsoluteDirectoryPath);
Guard.AssertFilePathWithinParentDirectory(RepositoryDirectory, destAbsoluteDirectoryPath);

Directory.Move(sourceAbsoluteDirectoryPath, destAbsoluteDirectoryPath);
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions backend/src/Designer/Infrastructure/MvcConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Altinn.Studio.Designer.Filters.DataModeling;
using Altinn.Studio.Designer.Filters.Git;
using Altinn.Studio.Designer.Filters.IO;
using Altinn.Studio.Designer.Filters.Options;
using Altinn.Studio.Designer.ModelBinding;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -26,6 +27,7 @@ public static IServiceCollection ConfigureMvc(this IServiceCollection services)
options.Filters.Add(typeof(DataModelingExceptionFilterAttribute));
options.Filters.Add(typeof(GitExceptionFilterAttribute));
options.Filters.Add(typeof(IoExceptionFilterAttribute));
options.Filters.Add(typeof(OptionsExceptionFilterAttribute));
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()));

Expand Down
1 change: 0 additions & 1 deletion backend/src/Designer/Infrastructure/ServiceRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public static IServiceCollection RegisterServiceImplementations(this IServiceCol
services.AddTransient<IAppDevelopmentService, AppDevelopmentService>();
services.AddTransient<IPreviewService, PreviewService>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IResourceRegistry, ResourceRegistryService>();
services.AddTransient<IProcessModelingService, ProcessModelingService>();
services.AddTransient<IImagesService, ImagesService>();
services.RegisterDatamodeling(configuration);
Expand Down
11 changes: 5 additions & 6 deletions backend/src/Designer/Models/Option.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

Expand All @@ -12,17 +11,17 @@ public class Option
/// <summary>
/// Value that connects the option to the data model.
/// </summary>
[Required]
[NotNullable]
[JsonPropertyName("value")]
[JsonConverter(typeof(OptionConverter))]
public object Value { get; set; }
[JsonConverter(typeof(OptionValueConverter))]
public required object Value { get; set; }

/// <summary>
/// Label to present to the user.
/// </summary>
[Required]
[NotNullable]
[JsonPropertyName("label")]
public string Label { get; set; }
public required string Label { get; set; }

/// <summary>
/// Description, typically displayed below the label.
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Designer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration
services.Configure<MaskinportenClientSettings>(configuration.GetSection("MaskinportenClientSettings"));
var maskinPortenClientName = "MaskinportenClient";
services.RegisterMaskinportenClientDefinition<MaskinPortenClientDefinition>(maskinPortenClientName, configuration.GetSection("MaskinportenClientSettings"));
services.AddHttpClient<IResourceRegistry, ResourceRegistryService>().AddMaskinportenHttpMessageHandler<MaskinPortenClientDefinition>(maskinPortenClientName);
services.AddHttpClient<IResourceRegistry, ResourceRegistryService>();

var maskinportenSettings = new MaskinportenClientSettings();
configuration.GetSection("MaskinportenClientSettings").Bind(maskinportenSettings);
Expand Down
Loading

0 comments on commit d54007e

Please sign in to comment.