fix(api): validate date-range month/day bounds on playout PUT actions
AlternateScheduleSelector.GetScheduleForDate constructs
new DateTime(year, StartMonth, StartDay) and only recovers from an
out-of-range *day* (rolling to the 1st of the next month); an out-of-range
*month* (e.g. 0 or 13) throws again from inside that recovery path and is
never caught, crashing playout building.
Validate StartMonth/EndMonth (1..12) and StartDay/EndDay (1..31) on both
PUT /api/playouts/{id}/alternate-schedules and PUT /api/playouts/{id}/templates
whenever an item sets LimitToDateRange, returning 422 before dispatching to
the mediator. Both actions already documented 422 in their ProducesResponseType
metadata.
This commit is contained in:
@@ -306,6 +306,11 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
||||
return BaseError.New("[Items] must contain at least one alternate schedule").ToErrorResult();
|
||||
}
|
||||
|
||||
foreach (BaseError error in ValidateDateRanges(items.Select(ToDateRangeCheck)))
|
||||
{
|
||||
return error.ToErrorResult();
|
||||
}
|
||||
|
||||
List<ProgramScheduleViewModel> schedules =
|
||||
await mediator.Send(new GetAllProgramSchedules(), cancellationToken);
|
||||
var scheduleIds = schedules.Select(s => s.Id).ToHashSet();
|
||||
@@ -389,6 +394,11 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
||||
|
||||
List<PlayoutTemplateItemRequest> items = request.Items ?? [];
|
||||
|
||||
foreach (BaseError error in ValidateDateRanges(items.Select(ToDateRangeCheck)))
|
||||
{
|
||||
return error.ToErrorResult();
|
||||
}
|
||||
|
||||
List<TemplateViewModel> templates = await mediator.Send(new GetAllTemplates(), cancellationToken);
|
||||
var templateIds = templates.Select(t => t.Id).ToHashSet();
|
||||
var missingTemplateIds = items.Select(i => i.TemplateId).Distinct()
|
||||
@@ -444,6 +454,53 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
||||
return result.ToDeletedResult();
|
||||
}
|
||||
|
||||
// Guards against AlternateScheduleSelector crashing on out-of-range dates: it constructs
|
||||
// `new DateTime(year, StartMonth, StartDay)` and only recovers from an overflowing *day*
|
||||
// (rolling to the 1st of the next month); an out-of-range *month* throws again from inside
|
||||
// that recovery path and is never caught. Only checked when LimitToDateRange is set, since
|
||||
// the fields are ignored otherwise.
|
||||
private static (bool LimitToDateRange, int StartMonth, int StartDay, int EndMonth, int EndDay) ToDateRangeCheck(
|
||||
PlayoutAlternateScheduleItemRequest item) =>
|
||||
(item.LimitToDateRange, item.StartMonth, item.StartDay, item.EndMonth, item.EndDay);
|
||||
|
||||
private static (bool LimitToDateRange, int StartMonth, int StartDay, int EndMonth, int EndDay) ToDateRangeCheck(
|
||||
PlayoutTemplateItemRequest item) =>
|
||||
(item.LimitToDateRange, item.StartMonth, item.StartDay, item.EndMonth, item.EndDay);
|
||||
|
||||
private static Option<BaseError> ValidateDateRanges(
|
||||
IEnumerable<(bool LimitToDateRange, int StartMonth, int StartDay, int EndMonth, int EndDay)> items)
|
||||
{
|
||||
foreach ((bool limitToDateRange, int startMonth, int startDay, int endMonth, int endDay) in items)
|
||||
{
|
||||
if (!limitToDateRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (startMonth is < 1 or > 12)
|
||||
{
|
||||
return Some(BaseError.New($"[StartMonth] {startMonth} must be between 1 and 12"));
|
||||
}
|
||||
|
||||
if (endMonth is < 1 or > 12)
|
||||
{
|
||||
return Some(BaseError.New($"[EndMonth] {endMonth} must be between 1 and 12"));
|
||||
}
|
||||
|
||||
if (startDay is < 1 or > 31)
|
||||
{
|
||||
return Some(BaseError.New($"[StartDay] {startDay} must be between 1 and 31"));
|
||||
}
|
||||
|
||||
if (endDay is < 1 or > 31)
|
||||
{
|
||||
return Some(BaseError.New($"[EndDay] {endDay} must be between 1 and 31"));
|
||||
}
|
||||
}
|
||||
|
||||
return Option<BaseError>.None;
|
||||
}
|
||||
|
||||
private static PlayoutResponseModel ToResponse(PlayoutNameViewModel vm) =>
|
||||
PlayoutResponseModel.From(
|
||||
vm.PlayoutId,
|
||||
|
||||
Reference in New Issue
Block a user