feat: добавлен фильтр для скрытия ссылок на занятия от неавторизованных пользователей
This commit is contained in:
1 parent
03560b0dc8
commit
cfd9196083
3 files changed
+68
-1
No files matched your search
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace SfeduSchedule.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Скрывает ссылки на подключение к занятиям от пользователей без Microsoft-сессии.
|
||||
/// </summary>
|
||||
public static class ScheduleLocationFilter
|
||||
{
|
||||
private const string AuthorizationRequiredMessage = "авторизуйтесь для просмотра";
|
||||
private static readonly Regex AbsoluteUriPattern = new(
|
||||
@"(?<![A-Za-z0-9+.-])[A-Za-z][A-Za-z0-9+.-]*:[^\s<>\[\]()]+",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает исходное расписание авторизованному пользователю или заменяет значение
|
||||
/// <c>customLocation</c> безопасной заглушкой, если оно содержит абсолютный URI.
|
||||
/// </summary>
|
||||
/// <param name="schedule">JSON расписания Modeus.</param>
|
||||
/// <param name="httpContext">Контекст запроса, содержащий Microsoft authentication cookie.</param>
|
||||
/// <returns>Исходный либо отфильтрованный JSON; <see langword="null"/>, если расписание отсутствует.</returns>
|
||||
/// <exception cref="JsonException">JSON расписания пуст или имеет некорректный формат.</exception>
|
||||
public static async Task<string?> FilterAsync(string? schedule, HttpContext httpContext)
|
||||
{
|
||||
if (schedule == null)
|
||||
return null;
|
||||
|
||||
// API key не даёт доступ к ссылкам: проверяем только cookie, созданную Microsoft OIDC-входом.
|
||||
var authenticationResult =
|
||||
await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
if (authenticationResult.Succeeded)
|
||||
return schedule;
|
||||
|
||||
var root = JsonNode.Parse(schedule) ?? throw new JsonException("Schedule response is empty.");
|
||||
if (root["_embedded"]?["event-locations"] is not JsonArray eventLocations)
|
||||
return root.ToJsonString();
|
||||
|
||||
// Если ссылка окружена текстом или Markdown-разметкой, скрываем всё значение customLocation.
|
||||
foreach (var eventLocation in eventLocations.OfType<JsonObject>())
|
||||
{
|
||||
if (eventLocation["customLocation"] is not JsonValue value ||
|
||||
!value.TryGetValue<string>(out var customLocation) ||
|
||||
string.IsNullOrWhiteSpace(customLocation) ||
|
||||
!ContainsAbsoluteUri(customLocation))
|
||||
continue;
|
||||
|
||||
eventLocation["customLocation"] = AuthorizationRequiredMessage;
|
||||
}
|
||||
|
||||
return root.ToJsonString();
|
||||
}
|
||||
|
||||
private static bool ContainsAbsoluteUri(string value)
|
||||
{
|
||||
return AbsoluteUriPattern.Matches(value)
|
||||
.Any(match => Uri.TryCreate(match.Value, UriKind.Absolute, out _));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user