Skip to content

Commit

Permalink
Introduce new method in IData for updating BinaryData (#228) (#231)
Browse files Browse the repository at this point in the history
* Introduce new method in IData for updating BinaryData without a HttpRequest as input. Old method marked as obsolete

* Consolidate test classes and replace JwtTokenUtil with UserTokenProvider

* Fix build errors

* Add some more tests and fix nullability warnings
  • Loading branch information
tjololo authored Apr 24, 2023
1 parent 2761cc7 commit 2cecad6
Show file tree
Hide file tree
Showing 6 changed files with 612 additions and 208 deletions.
19 changes: 14 additions & 5 deletions src/Altinn.App.Api/Controllers/DataController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
using Altinn.Platform.Storage.Interface.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

namespace Altinn.App.Api.Controllers
{
Expand Down Expand Up @@ -263,7 +265,7 @@ public async Task<ActionResult> Put(
return errorResponse;
}

return await PutBinaryData(org, app, instanceOwnerPartyId, instanceGuid, dataGuid);
return await PutBinaryData(instanceOwnerPartyId, instanceGuid, dataGuid);
}
catch (PlatformHttpException e)
{
Expand Down Expand Up @@ -513,12 +515,19 @@ private async Task<ActionResult> GetFormData(
return Ok(appModel);
}

private async Task<ActionResult> PutBinaryData(string org, string app, int instanceOwnerPartyId, Guid instanceGuid, Guid dataGuid)
private async Task<ActionResult> PutBinaryData(int instanceOwnerPartyId, Guid instanceGuid, Guid dataGuid)
{
DataElement dataElement = await _dataClient.UpdateBinaryData(org, app, instanceOwnerPartyId, instanceGuid, dataGuid, Request);
SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request);
if (Request.Headers.TryGetValue("Content-Disposition", out StringValues headerValues))
{
var contentDispositionHeader = ContentDispositionHeaderValue.Parse(headerValues.ToString());
_logger.LogInformation("Content-Disposition: {ContentDisposition}", headerValues);
DataElement dataElement = await _dataClient.UpdateBinaryData(new InstanceIdentifier(instanceOwnerPartyId, instanceGuid), Request.ContentType, contentDispositionHeader.FileName.ToString(), dataGuid, Request.Body);
SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request);

return Created(dataElement.SelfLinks.Apps, dataElement);
return Created(dataElement.SelfLinks.Apps, dataElement);
}

return BadRequest("Invalid data provided. Error: The request must include a Content-Disposition header");
}

private async Task<ActionResult> PutFormData(string org, string app, Instance instance, Guid dataGuid, string dataType)
Expand Down
59 changes: 40 additions & 19 deletions src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using Newtonsoft.Json;
using System.Xml;
using Microsoft.IdentityModel.Tokens;

namespace Altinn.App.Core.Infrastructure.Clients.Storage
{
Expand All @@ -31,35 +32,31 @@ public class DataClient : IData
{
private readonly PlatformSettings _platformSettings;
private readonly ILogger _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly AppSettings _settings;
private readonly IUserTokenProvider _userTokenProvider;
private readonly HttpClient _client;

/// <summary>
/// Initializes a new data of the <see cref="DataClient"/> class.
/// </summary>
/// <param name="platformSettings">the platform settings</param>
/// <param name="logger">the logger</param>
/// <param name="httpContextAccessor">The http context accessor </param>
/// <param name="settings">The current app settings.</param>
/// <param name="httpClient">A HttpClient from the built in HttpClient factory.</param>
/// <param name="userTokenProvider">Service to obtain json web token</param>
public DataClient(
IOptions<PlatformSettings> platformSettings,
ILogger<DataClient> logger,
IHttpContextAccessor httpContextAccessor,
IOptionsMonitor<AppSettings> settings,
HttpClient httpClient)
HttpClient httpClient,
IUserTokenProvider userTokenProvider)
{
_platformSettings = platformSettings.Value;
_logger = logger;
_httpContextAccessor = httpContextAccessor;
_settings = settings.CurrentValue;

httpClient.BaseAddress = new Uri(_platformSettings.ApiStorageEndpoint);
httpClient.DefaultRequestHeaders.Add(General.SubscriptionKeyHeaderName, _platformSettings.SubscriptionKey);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
_client = httpClient;
_userTokenProvider = userTokenProvider;
}

/// <inheritdoc />
Expand All @@ -77,7 +74,7 @@ public async Task<DataElement> InsertFormData<T>(T dataToSerialize, Guid instanc
public async Task<DataElement> InsertFormData<T>(Instance instance, string dataType, T dataToSerialize, Type type)
{
string apiUrl = $"instances/{instance.Id}/data?dataType={dataType}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();
DataElement dataElement;

using MemoryStream stream = new MemoryStream();
Expand Down Expand Up @@ -105,7 +102,7 @@ public async Task<DataElement> UpdateData<T>(T dataToSerialize, Guid instanceGui
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"instances/{instanceIdentifier}/data/{dataId}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

using MemoryStream stream = new MemoryStream();

Expand Down Expand Up @@ -147,7 +144,7 @@ public async Task<Stream> GetBinaryData(string org, string app, int instanceOwne
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"instances/{instanceIdentifier}/data/{dataId}";

string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

HttpResponseMessage response = await _client.GetAsync(token, apiUrl);

Expand All @@ -168,7 +165,7 @@ public async Task<object> GetFormData(Guid instanceGuid, Type type, string org,
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"instances/{instanceIdentifier}/data/{dataId}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

HttpResponseMessage response = await _client.GetAsync(token, apiUrl);
if (response.IsSuccessStatusCode)
Expand All @@ -194,7 +191,7 @@ public async Task<List<AttachmentList>> GetBinaryDataList(string org, string app
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"instances/{instanceIdentifier}/dataelements";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

DataElementList dataList;
List<AttachmentList> attachmentList = new List<AttachmentList>();
Expand Down Expand Up @@ -259,7 +256,7 @@ public async Task<bool> DeleteData(string org, string app, int instanceOwnerPart
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"instances/{instanceIdentifier}/data/{dataGuid}?delay={delay}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

HttpResponseMessage response = await _client.DeleteAsync(token, apiUrl);

Expand All @@ -277,7 +274,7 @@ public async Task<DataElement> InsertBinaryData(string org, string app, int inst
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instanceIdentifier}/data?dataType={dataType}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();
DataElement dataElement;

StreamContent content = CreateContentStream(request);
Expand All @@ -300,7 +297,7 @@ public async Task<DataElement> InsertBinaryData(string org, string app, int inst
public async Task<DataElement> InsertBinaryData(string instanceId, string dataType, string contentType, string filename, Stream stream)
{
string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instanceId}/data?dataType={dataType}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();
DataElement dataElement;

StreamContent content = new StreamContent(stream);
Expand Down Expand Up @@ -333,7 +330,7 @@ public async Task<DataElement> UpdateBinaryData(string org, string app, int inst
{
string instanceIdentifier = $"{instanceOwnerPartyId}/{instanceGuid}";
string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instanceIdentifier}/data/{dataGuid}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

StreamContent content = CreateContentStream(request);

Expand All @@ -350,6 +347,30 @@ public async Task<DataElement> UpdateBinaryData(string org, string app, int inst
_logger.LogError($"Updating attachment {dataGuid} for instance {instanceGuid} failed with status code {response.StatusCode}");
throw await PlatformHttpException.CreateAsync(response);
}

/// <inheritdoc />
public async Task<DataElement> UpdateBinaryData(InstanceIdentifier instanceIdentifier, string? contentType, string filename, Guid dataGuid, Stream stream)
{
string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instanceIdentifier}/data/{dataGuid}";
string token = _userTokenProvider.GetUserToken();
StreamContent content = new StreamContent(stream);
content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
content.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
{
FileName = filename,
FileNameStar = filename
};
HttpResponseMessage response = await _client.PutAsync(token, apiUrl, content);
_logger.LogInformation("Update binary data result: {ResultCode}", response.StatusCode);
if (response.IsSuccessStatusCode)
{
string instancedata = await response.Content.ReadAsStringAsync();
DataElement dataElement = JsonConvert.DeserializeObject<DataElement>(instancedata)!;

return dataElement;
}
throw await PlatformHttpException.CreateAsync(response);
}

private static StreamContent CreateContentStream(HttpRequest request)
{
Expand All @@ -368,7 +389,7 @@ private static StreamContent CreateContentStream(HttpRequest request)
public async Task<DataElement> Update(Instance instance, DataElement dataElement)
{
string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instance.Id}/dataelements/{dataElement.Id}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName);
string token = _userTokenProvider.GetUserToken();

StringContent jsonString = new StringContent(JsonConvert.SerializeObject(dataElement), Encoding.UTF8, "application/json");
HttpResponseMessage response = await _client.PutAsync(token, apiUrl, jsonString);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Altinn.App.Core.Infrastructure.Clients.Storage.TestData;

/// <summary>
/// Example Model used in tests
/// </summary>
public class ExampleModel
{
/// <summary>
/// The name
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// The age
/// </summary>
public int Age { get; set; } = 0;
}
13 changes: 12 additions & 1 deletion src/Altinn.App.Core/Interface/IData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,21 @@ public interface IData
/// <param name="instanceGuid">The instance id</param>
/// <param name="dataGuid">The data id</param>
/// <param name="request">Http request containing the attachment to be saved</param>
[Obsolete(message:"Deprecated please use UpdateBinaryData(InstanceIdentifier, string, string, Guid, Stream) instead", error: false)]
Task<DataElement> UpdateBinaryData(string org, string app, int instanceOwnerPartyId, Guid instanceGuid, Guid dataGuid, HttpRequest request);

/// <summary>
/// Updates a binary data element.
/// Method that updates a form attachments to disk/storage and returns the updated data element.
/// </summary>
/// <param name="instanceIdentifier">Instance identifier instanceOwnerPartyId and instanceGuid</param>
/// <param name="contentType">Content type of the updated binary data</param>
/// <param name="filename">Filename of the updated binary data</param>
/// <param name="dataGuid">Guid of the data element to update</param>
/// <param name="stream">Updated binary data</param>
Task<DataElement> UpdateBinaryData(InstanceIdentifier instanceIdentifier, string? contentType, string filename, Guid dataGuid, Stream stream);

/// <summary>
/// Insert a binary data element.
/// </summary>
/// <param name="instanceId">isntanceId = {instanceOwnerPartyId}/{instanceGuid}</param>
/// <param name="dataType">data type</param>
Expand Down
Loading

0 comments on commit 2cecad6

Please sign in to comment.