-
Notifications
You must be signed in to change notification settings - Fork 11
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
Ivarne/warning fixes #328
Ivarne/warning fixes #328
Changes from all commits
c092b50
903e8d6
01d7f40
d130cab
de0ef73
1c73f06
a3dc24a
b7bb5c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
|
||
using System.Net; | ||
using System.Security.Claims; | ||
using Altinn.App.Api.Helpers.RequestHandling; | ||
|
@@ -30,6 +32,7 @@ | |
/// The data controller handles creation, update, validation and calculation of data elements. | ||
/// </summary> | ||
[AutoValidateAntiforgeryTokenIfAuthCookie] | ||
[ApiController] | ||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] | ||
[Route("{org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceGuid:guid}/data")] | ||
public class DataController : ControllerBase | ||
|
@@ -113,18 +116,13 @@ | |
[FromRoute] Guid instanceGuid, | ||
[FromQuery] string dataType) | ||
{ | ||
if (string.IsNullOrWhiteSpace(dataType)) | ||
{ | ||
return BadRequest("Element type must be provided."); | ||
} | ||
|
||
/* The Body of the request is read much later when it has been made sure it is worth it. */ | ||
|
||
try | ||
{ | ||
Application application = await _appMetadata.GetApplicationMetadata(); | ||
DataType dataTypeFromMetadata = application.DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
|
||
DataType? dataTypeFromMetadata = application.DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
|
||
if (dataTypeFromMetadata == null) | ||
{ | ||
|
@@ -158,7 +156,7 @@ | |
(bool validationRestrictionSuccess, List<ValidationIssue> errors) = DataRestrictionValidation.CompliesWithDataRestrictions(Request, dataTypeFromMetadata); | ||
if (!validationRestrictionSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(errors)); | ||
return BadRequest(await GetErrorDetails(errors)); | ||
} | ||
|
||
StreamContent streamContent = Request.CreateContentStream(); | ||
|
@@ -175,11 +173,11 @@ | |
Description = errorMessage | ||
}; | ||
_logger.LogError(errorMessage); | ||
return new BadRequestObjectResult(await GetErrorDetails(new List<ValidationIssue> { error })); | ||
return BadRequest(await GetErrorDetails(new List<ValidationIssue> { error })); | ||
} | ||
|
||
bool parseSuccess = Request.Headers.TryGetValue("Content-Disposition", out StringValues headerValues); | ||
string filename = parseSuccess ? DataRestrictionValidation.GetFileNameFromHeader(headerValues) : string.Empty; | ||
string? filename = parseSuccess ? DataRestrictionValidation.GetFileNameFromHeader(headerValues) : null; | ||
|
||
IEnumerable<FileAnalysisResult> fileAnalysisResults = new List<FileAnalysisResult>(); | ||
if (FileAnalysisEnabledForDataType(dataTypeFromMetadata)) | ||
|
@@ -196,7 +194,7 @@ | |
|
||
if (!fileValidationSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(validationIssues)); | ||
return BadRequest(await GetErrorDetails(validationIssues)); | ||
} | ||
|
||
fileStream.Seek(0, SeekOrigin.Begin); | ||
|
@@ -258,7 +256,7 @@ | |
return NotFound($"Did not find instance {instance}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -319,7 +317,7 @@ | |
return Conflict($"Cannot update data element of archived or deleted instance {instanceOwnerPartyId}/{instanceGuid}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -332,19 +330,19 @@ | |
|
||
if (appLogic == null) | ||
{ | ||
_logger.LogError($"Could not determine if {dataType} requires app logic for application {org}/{app}"); | ||
_logger.LogError("Could not determine if {dataType} requires app logic for application {org}/{app}", dataType, org, app); | ||
Check failure Code scanning / CodeQL Log entries created from user input High
This log entry depends on a
user-provided value Error loading related location Loading This log entry depends on a user-provided value Error loading related location Loading There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. log4j: Ooof, we use application insights that shouldn't have any of these issues, but application owners might use other tools. I don't think there is anything we can do preventively here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, this change does not make it worse/better and we have alot of log4shell warnings/errors Check failure Code scanning / CodeQL Log entries created from user input High
This log entry depends on a
user-provided value Error loading related location Loading Check failure Code scanning / CodeQL Log entries created from user input High
This log entry depends on a
user-provided value Error loading related location Loading |
||
return BadRequest($"Could not determine if data type {dataType} requires application logic."); | ||
} | ||
else if ((bool)appLogic) | ||
else if (appLogic == true) | ||
{ | ||
return await PutFormData(org, app, instance, dataGuid, dataType); | ||
} | ||
|
||
DataType dataTypeFromMetadata = (await _appMetadata.GetApplicationMetadata()).DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
DataType? dataTypeFromMetadata = (await _appMetadata.GetApplicationMetadata()).DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
(bool validationRestrictionSuccess, List<ValidationIssue> errors) = DataRestrictionValidation.CompliesWithDataRestrictions(Request, dataTypeFromMetadata); | ||
if (!validationRestrictionSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(errors)); | ||
return BadRequest(await GetErrorDetails(errors)); | ||
} | ||
|
||
return await PutBinaryData(instanceOwnerPartyId, instanceGuid, dataGuid); | ||
|
@@ -386,7 +384,7 @@ | |
return Conflict($"Cannot delete data element of archived or deleted instance {instanceOwnerPartyId}/{instanceGuid}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.Find(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.Find(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -421,21 +419,19 @@ | |
{ | ||
_logger.LogError(exception, message); | ||
|
||
if (exception is PlatformHttpException) | ||
if (exception is PlatformHttpException phe) | ||
{ | ||
PlatformHttpException phe = exception as PlatformHttpException; | ||
return StatusCode((int)phe.Response.StatusCode, phe.Message); | ||
} | ||
else if (exception is ServiceException) | ||
else if (exception is ServiceException se) | ||
{ | ||
ServiceException se = exception as ServiceException; | ||
return StatusCode((int)se.StatusCode, se.Message); | ||
} | ||
|
||
return StatusCode(500, $"{message}"); | ||
} | ||
|
||
private async Task<ActionResult> CreateBinaryData(Instance instanceBefore, string dataType, string contentType, string filename, Stream fileStream) | ||
private async Task<ActionResult> CreateBinaryData(Instance instanceBefore, string dataType, string contentType, string? filename, Stream fileStream) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked the call chain for this method in regards to filename beeing null. Seems to me that DataRestrictionValidation.CompliesWithDataRestrictions that is called from the Post Data method should ensure that the filename is not null, but it's not quite obvious so the compiler might not catch up on it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I only traced it to being read from a header I think is optional, so I figured it might be null. We should probably store the file with a filename anyway, so a better solution to the nullability problem might be to throw or use |
||
{ | ||
int instanceOwnerPartyId = int.Parse(instanceBefore.Id.Split("/")[0]); | ||
Guid instanceGuid = Guid.Parse(instanceBefore.Id.Split("/")[1]); | ||
|
@@ -459,7 +455,7 @@ | |
{ | ||
Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); | ||
|
||
object appModel; | ||
object? appModel; | ||
|
||
string classRef = _appResourcesService.GetClassRefForLogicDataType(dataType); | ||
|
||
|
@@ -472,7 +468,7 @@ | |
ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); | ||
appModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
|
||
if (!string.IsNullOrEmpty(deserializer.Error)) | ||
if (!string.IsNullOrEmpty(deserializer.Error) || appModel is null) | ||
{ | ||
return BadRequest(deserializer.Error); | ||
} | ||
|
@@ -510,7 +506,7 @@ | |
|
||
if (dataStream != null) | ||
{ | ||
string userOrgClaim = User.GetOrg(); | ||
string? userOrgClaim = User.GetOrg(); | ||
if (userOrgClaim == null || !org.Equals(userOrgClaim, StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
await _instanceClient.UpdateReadStatus(instanceOwnerPartyId, instanceGuid, "read"); | ||
|
@@ -588,7 +584,7 @@ | |
|
||
await _dataProcessor.ProcessDataRead(instance, dataGuid, appModel); | ||
|
||
string userOrgClaim = User.GetOrg(); | ||
string? userOrgClaim = User.GetOrg(); | ||
if (userOrgClaim == null || !org.Equals(userOrgClaim, StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
await _instanceClient.UpdateReadStatus(instanceOwnerId, instanceGuid, "read"); | ||
|
@@ -602,7 +598,7 @@ | |
if (Request.Headers.TryGetValue("Content-Disposition", out StringValues headerValues)) | ||
{ | ||
var contentDispositionHeader = ContentDispositionHeaderValue.Parse(headerValues.ToString()); | ||
_logger.LogInformation("Content-Disposition: {ContentDisposition}", headerValues); | ||
_logger.LogInformation("Content-Disposition: {ContentDisposition}", headerValues.ToString()); | ||
DataElement dataElement = await _dataClient.UpdateBinaryData(new InstanceIdentifier(instanceOwnerPartyId, instanceGuid), Request.ContentType, contentDispositionHeader.FileName.ToString(), dataGuid, Request.Body); | ||
SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request); | ||
|
||
|
@@ -620,7 +616,7 @@ | |
Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); | ||
|
||
ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); | ||
object serviceModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
object? serviceModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
|
||
if (!string.IsNullOrEmpty(deserializer.Error)) | ||
{ | ||
|
@@ -632,7 +628,7 @@ | |
return BadRequest("No data found in content"); | ||
} | ||
|
||
Dictionary<string, object> changedFields = await JsonHelper.ProcessDataWriteWithDiff(instance, dataGuid, serviceModel, _dataProcessor, _logger); | ||
Dictionary<string, object?>? changedFields = await JsonHelper.ProcessDataWriteWithDiff(instance, dataGuid, serviceModel, _dataProcessor, _logger); | ||
|
||
await UpdatePresentationTextsOnInstance(instance, dataType, serviceModel); | ||
await UpdateDataValuesOnInstance(instance, dataType, serviceModel); | ||
|
@@ -652,8 +648,10 @@ | |
string dataUrl = updatedDataElement.SelfLinks.Apps; | ||
if (changedFields is not null) | ||
{ | ||
CalculationResult calculationResult = new CalculationResult(updatedDataElement); | ||
calculationResult.ChangedFields = changedFields; | ||
CalculationResult calculationResult = new(updatedDataElement) | ||
{ | ||
ChangedFields = changedFields | ||
}; | ||
return StatusCode((int)HttpStatusCode.SeeOther, calculationResult); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change the error response to the user as not providing dataType param will lead to BadReuqest from line 128
Element type {dataType} not allowed for instance {instanceOwnerPartyId}/{instanceGuid}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the addition
#nullable enable
on line 1 will trigger asp net to returnBadRequest
with aProblem
json structure that explains thatdataType
is a required part of the query string, because it isn't nullable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested this? Might be som error in my test setup, but when I tested it locally it didn't return bad request with Problem json when I omitted the dataType param
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... Not here specifically. I got that behaviour (breaking a test) when running tests under net8.0 with
<nullable>enabled</nullable>
in project.json. Therefore I was very careful with [FromQuery] parameters that was not nullable. Maybe it's only when nullability is enabled globally, maybe only net8.0?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You were right. The difference was that I tested this with
OptionsController
and it was annotated with[ApiController]
which causes automatic BadRequest for non nullable query strings when nullable is enabled. I wrote a test for this "BadRequest" response and added the annotation to make the test work in ab43649.There is no difference between dovnet 6-8 or file scoped/project scoped nullable