From 768c6a0e3d871e32ba71ee8de3b26428f7282196 Mon Sep 17 00:00:00 2001 From: Pavel Zhur Date: Mon, 24 Jun 2024 13:29:29 +0300 Subject: [PATCH] downstream sources tenants tags matching --- .../Functions/V1/GetSong.cs | 2 +- .../Functions/V1/GetSourcesAndExternalIds.cs | 2 +- .../HarmonyDB.Index.Api/Functions/V1/Ping.cs | 3 +- .../Functions/VDev/IndexFunctions.cs | 7 +- .../Functions/VInternal/GetLyrics.cs | 19 ++--- .../Functions/VInternal/TryImport.cs | 21 ++--- .../Functions/VPublic/GetPublic.cs | 22 +++-- .../Services/CommonExecutions.cs | 14 ++-- .../DownstreamApiClient.cs | 39 ++++++--- .../DownstreamApiClientOptions.cs | 4 +- ...armonyDB.Index.DownstreamApi.Client.csproj | 3 +- .../AuthorizationApiClient.cs | 10 +++ .../AuthorizationApiClientOptions.cs | 3 + .../AnonymousFunctionBase.cs | 36 +++++++++ .../AuthorizationFunctionBase.cs | 23 +++--- ...eShelf.Common.Api.WithAuthorization.csproj | 3 +- .../SecurityContext.cs | 80 +++++++++++++++++-- .../ServiceFunctionBase.cs | 36 +++++++++ .../OneShelf.Common.Api/FunctionBase.cs | 6 +- .../AuthorizationQuickChecker.cs | 19 +++-- .../Functions/V3/GetPdfs.cs | 6 +- .../Functions/V3/PreviewPdf.cs | 2 +- 22 files changed, 266 insertions(+), 94 deletions(-) create mode 100644 OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AnonymousFunctionBase.cs create mode 100644 OneShelf.Common/OneShelf.Common.Api.WithAuthorization/ServiceFunctionBase.cs diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSong.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSong.cs index 033cecdb..4f5767bc 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSong.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSong.cs @@ -25,6 +25,6 @@ public Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post" => RunHandler(req, request); protected override async Task Execute(HttpRequest httpRequest, GetSongRequest request) - => await _commonExecutions.GetSong(request); + => await _commonExecutions.GetSong(request.ExternalId); } } diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSourcesAndExternalIds.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSourcesAndExternalIds.cs index 8bb5a257..758b4e21 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSourcesAndExternalIds.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/GetSourcesAndExternalIds.cs @@ -27,5 +27,5 @@ public Task Run( protected override async Task Execute(HttpRequest httpRequest, GetSourcesAndExternalIdsRequest request) - => await _commonExecutions.GetSourcesAndExternalIds(request); + => await _commonExecutions.GetSourcesAndExternalIds(request.Uris); } \ No newline at end of file diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/Ping.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/Ping.cs index a82259e9..bb834acf 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/Ping.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/V1/Ping.cs @@ -5,6 +5,7 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; +using OneShelf.Common.Api.WithAuthorization; namespace HarmonyDB.Index.Api.Functions.V1 { @@ -24,7 +25,7 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou { _logger.LogInformation("C# HTTP trigger function processed a request."); - await Task.WhenAll(Enumerable.Range(0, _downstreamApiClient.DownstreamSourcesCount).Select(x => _downstreamApiClient.V1Ping(x))); + await _downstreamApiClient.PingAll(); var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VDev/IndexFunctions.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VDev/IndexFunctions.cs index 366356af..85802259 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VDev/IndexFunctions.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VDev/IndexFunctions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; +using OneShelf.Common.Api.WithAuthorization; namespace HarmonyDB.Index.Api.Functions.VDev; @@ -14,13 +15,17 @@ public class IndexFunctions private readonly DownstreamApiClient _downstreamApiClient; private readonly ProgressionsCache _progressionsCache; private readonly LoopsStatisticsCache _loopsStatisticsCache; + private readonly SecurityContext _securityContext; - public IndexFunctions(ILogger logger, DownstreamApiClient downstreamApiClient, ProgressionsCache progressionsCache, LoopsStatisticsCache loopsStatisticsCache) + public IndexFunctions(ILogger logger, DownstreamApiClient downstreamApiClient, ProgressionsCache progressionsCache, LoopsStatisticsCache loopsStatisticsCache, SecurityContext securityContext) { _logger = logger; _downstreamApiClient = downstreamApiClient; _progressionsCache = progressionsCache; _loopsStatisticsCache = loopsStatisticsCache; + _securityContext = securityContext; + + _securityContext.InitService(); } [Function(nameof(VDevSaveProgressionsIndex))] diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/GetLyrics.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/GetLyrics.cs index 26242b18..6d27afec 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/GetLyrics.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/GetLyrics.cs @@ -7,15 +7,16 @@ using Microsoft.Extensions.Logging; using OneShelf.Common; using OneShelf.Common.Api; +using OneShelf.Common.Api.WithAuthorization; namespace HarmonyDB.Index.Api.Functions.VInternal; -public class GetLyrics : FunctionBase +public class GetLyrics : ServiceFunctionBase { private readonly CommonExecutions _commonExecutions; - public GetLyrics(ILoggerFactory loggerFactory, CommonExecutions commonExecutions) - : base(loggerFactory) + public GetLyrics(ILoggerFactory loggerFactory, CommonExecutions commonExecutions, SecurityContext securityContext) + : base(loggerFactory, securityContext) { _commonExecutions = commonExecutions; } @@ -26,17 +27,9 @@ public Task Run([HttpTrigger(AuthorizationLevel.Function, "post") protected override async Task Execute(GetLyricsRequest request) { - var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new() - { - Identity = null!, - Uris = new Uri(request.Url).Once().ToList(), - })).Attributes.Single().Value.ExternalId; + var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new Uri(request.Url).Once().ToList())).Attributes.Single().Value.ExternalId; - var chords = (await _commonExecutions.GetSong(new() - { - Identity = null!, - ExternalId = externalId, - })).Song; + var chords = (await _commonExecutions.GetSong(externalId)).Song; return new() { diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/TryImport.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/TryImport.cs index 6926b84e..0dc6fa21 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/TryImport.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VInternal/TryImport.cs @@ -8,15 +8,18 @@ using Microsoft.Extensions.Logging; using OneShelf.Common; using OneShelf.Common.Api; +using OneShelf.Common.Api.WithAuthorization; +using System.Security; +using SecurityContext = OneShelf.Common.Api.WithAuthorization.SecurityContext; namespace HarmonyDB.Index.Api.Functions.VInternal; -public class TryImport : FunctionBase +public class TryImport : ServiceFunctionBase { private readonly CommonExecutions _commonExecutions; - public TryImport(ILoggerFactory loggerFactory, CommonExecutions commonExecutions) - : base(loggerFactory) + public TryImport(ILoggerFactory loggerFactory, CommonExecutions commonExecutions, SecurityContext securityContext) + : base(loggerFactory, securityContext) { _commonExecutions = commonExecutions; } @@ -29,17 +32,9 @@ protected override async Task Execute(TryImportRequest reques { try { - var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new() - { - Identity = null!, - Uris = new Uri(request.Url).Once().ToList(), - }))!.Attributes.Single().Value.ExternalId; + var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new Uri(request.Url).Once().ToList())).Attributes.Single().Value.ExternalId; - var chords = (await _commonExecutions.GetSong(new() - { - Identity = null!, - ExternalId = externalId, - }))!.Song; + var chords = (await _commonExecutions.GetSong(externalId)).Song; if (chords.Artists?.Count(x => !string.IsNullOrWhiteSpace(x)) is (null or 0) || string.IsNullOrWhiteSpace(chords.Title)) { diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VPublic/GetPublic.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VPublic/GetPublic.cs index f7fffff9..9f773324 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VPublic/GetPublic.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Functions/VPublic/GetPublic.cs @@ -13,16 +13,20 @@ using OneShelf.Collectives.Api.Model.VInternal; using OneShelf.Common; using OneShelf.Common.Api; +using OneShelf.Common.Api.WithAuthorization; +using System.Security; +using SecurityContext = OneShelf.Common.Api.WithAuthorization.SecurityContext; namespace HarmonyDB.Index.Api.Functions.VPublic; -public class GetPublic : FunctionBase +public class GetPublic : AnonymousFunctionBase { private readonly CommonExecutions _commonExecutions; private readonly CollectivesApiClient _collectivesApiClient; - public GetPublic(ILoggerFactory loggerFactory, CollectivesApiClient collectivesApiClient, CommonExecutions commonExecutions) - : base(loggerFactory) + public GetPublic(ILoggerFactory loggerFactory, CollectivesApiClient collectivesApiClient, + CommonExecutions commonExecutions, SecurityContext securityContext) + : base(loggerFactory, securityContext) { _collectivesApiClient = collectivesApiClient; _commonExecutions = commonExecutions; @@ -37,17 +41,9 @@ protected override async Task Execute(GetPublicRequest getPub Chords chords; try { - var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new() - { - Identity = null!, - Uris = new Uri(getPublicRequest.Url).Once().ToList(), - }))!.Attributes.Single().Value.ExternalId; + var externalId = (await _commonExecutions.GetSourcesAndExternalIds(new Uri(getPublicRequest.Url).Once().ToList())).Attributes.Single().Value.ExternalId; - chords = (await _commonExecutions.GetSong(new() - { - Identity = null!, - ExternalId = externalId, - }))!.Song; + chords = (await _commonExecutions.GetSong(externalId)).Song; if (!chords.IsPublic) return new() diff --git a/HarmonyDB.Index/HarmonyDB.Index.Api/Services/CommonExecutions.cs b/HarmonyDB.Index/HarmonyDB.Index.Api/Services/CommonExecutions.cs index 4cdf659b..1d246aed 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.Api/Services/CommonExecutions.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.Api/Services/CommonExecutions.cs @@ -1,7 +1,5 @@ using HarmonyDB.Index.DownstreamApi.Client; -using HarmonyDB.Source.Api.Client; using HarmonyDB.Source.Api.Model.V1.Api; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using OneShelf.Common; @@ -18,10 +16,10 @@ public CommonExecutions(ILogger logger, DownstreamApiClient do _downstreamApiClient = downstreamApiClient; } - public async Task GetSourcesAndExternalIds(GetSourcesAndExternalIdsRequest request) + public async Task GetSourcesAndExternalIds(IReadOnlyList uris) { - var results = await Task.WhenAll(Enumerable.Range(0, _downstreamApiClient.DownstreamSourcesCount) - .Select(x => _downstreamApiClient.V1GetSourcesAndExternalIds(request.Identity, x, request.Uris))); + var results = await Task.WhenAll(_downstreamApiClient.GetDownstreamSourceIndices(_ => true) + .Select(x => _downstreamApiClient.V1GetSourcesAndExternalIds(x, uris))); var all = results .WithIndices() @@ -40,10 +38,10 @@ public async Task GetSourcesAndExternalIds(Get }; } - public async Task GetSong(GetSongRequest request) + public async Task GetSong(string externalId) { - var sourceIndex = _downstreamApiClient.GetDownstreamSourceIndexByExternalId(request.ExternalId); - var getSongResponse = await _downstreamApiClient.V1GetSong(request.Identity, sourceIndex, request.ExternalId); + var sourceIndex = _downstreamApiClient.GetDownstreamSourceIndexByExternalId(externalId); + var getSongResponse = await _downstreamApiClient.V1GetSong(sourceIndex, externalId); getSongResponse.Song.Source = _downstreamApiClient.GetSourceTitle(getSongResponse.Song.Source); return getSongResponse; } diff --git a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClient.cs b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClient.cs index 67058d35..f674575e 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClient.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClient.cs @@ -5,36 +5,42 @@ using Microsoft.Extensions.Options; using OneShelf.Authorization.Api.Model; using OneShelf.Common; +using OneShelf.Common.Api.WithAuthorization; namespace HarmonyDB.Index.DownstreamApi.Client; public class DownstreamApiClient { + private readonly SecurityContext _securityContext; private readonly DownstreamApiClientOptions _options; private readonly IReadOnlyList _clients; - public DownstreamApiClient(IHttpClientFactory httpClientFactory, IOptions options) + public DownstreamApiClient(IHttpClientFactory httpClientFactory, IOptions options, SecurityContext securityContext) { + _securityContext = securityContext; _options = options.Value; _clients = _options.DownstreamSources.Select(o => new SourceApiClient(o, httpClientFactory)).ToList(); } - public int GetDownstreamSourceIndexBySourceKey(string sourceKey) => _options.DownstreamSources.WithIndices().Single(s => s.x.Sources.Any(s => s.Key == sourceKey)).i; + public int GetDownstreamSourceIndexBySourceKey(string sourceKey) => _options.DownstreamSources.WithIndices().Where(x => IsAvailable(x.x)).First(s => s.x.Sources.Any(s => s.Key == sourceKey)).i; - public IEnumerable GetDownstreamSourceIndices(Func selector) => _options.DownstreamSources.WithIndices().Where(x => selector(x.x)).Select(x => x.i); - - public int DownstreamSourcesCount => _options.DownstreamSources.Count; + public IEnumerable GetDownstreamSourceIndices(Func selector) => _options.DownstreamSources.WithIndices().Where(x => selector(x.x)).Where(x => IsAvailable(x.x)).Select(x => x.i); - public string GetSourceTitle(string sourceKey) => _options.DownstreamSources.SelectMany(x => x.Sources).Single(s => s.Key == sourceKey).Title; + public string GetSourceTitle(string sourceKey) => _options.DownstreamSources.Where(IsAvailable).SelectMany(x => x.Sources).Single(s => s.Key == sourceKey).Title; - public int GetDownstreamSourceIndexByExternalId(string externalId) => _options.DownstreamSources.WithIndices().Single(s => s.x.ExternalIdPrefixes.Any(externalId.StartsWith)).i; + public int GetDownstreamSourceIndexByExternalId(string externalId) => _options.DownstreamSources.WithIndices().Where(x => IsAvailable(x.x)).Single(s => s.x.ExternalIdPrefixes.Any(externalId.StartsWith)).i; + + public async Task PingAll() + { + await Task.WhenAll(Enumerable.Range(0, _options.DownstreamSources.Count).Select(V1Ping)); + } public async Task VInternalGetProgressionsIndex(int sourceIndex, GetProgressionsIndexRequest request, CancellationToken cancellationToken) => await _clients[sourceIndex].VInternalGetProgressionsIndex(request, cancellationToken); - public async Task V1GetSong(Identity identity, int sourceIndex, string externalId) - => await _clients[sourceIndex].V1GetSong(identity, externalId); + public async Task V1GetSong(int sourceIndex, string externalId) + => await _clients[sourceIndex].V1GetSong(_securityContext.OutputIdentity, externalId); public async Task V1GetSongs(Identity identity, int sourceIndex, IReadOnlyList externalIds) => await _clients[sourceIndex].V1GetSongs(identity, externalIds); @@ -48,6 +54,17 @@ public async Task V1Search(int sourceIndex, SearchRequest reques public async Task V1GetSearchHeader(Identity identity, int sourceIndex, string externalId) => await _clients[sourceIndex].V1GetSearchHeader(identity, externalId); - public async Task> V1GetSourcesAndExternalIds(Identity identity, int sourceIndex, IEnumerable uris) - => await _clients[sourceIndex].V1GetSourcesAndExternalIds(identity, uris); + public async Task> V1GetSourcesAndExternalIds(int sourceIndex, IEnumerable uris) + => await _clients[sourceIndex].V1GetSourcesAndExternalIds(_securityContext.OutputIdentity, uris); + + private bool IsAvailable(DownstreamApiClientOptions.DownstreamSourceOptions downstreamOptions) + { + var tags = _securityContext.TenantTags; + + if (downstreamOptions.TenantTagsForbidden.Intersect(tags).Any()) return false; + + if (downstreamOptions.TenantTagsRequired.Any() && downstreamOptions.TenantTagsRequired.Any(x => !tags.Contains(x))) return false; + + return true; + } } \ No newline at end of file diff --git a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClientOptions.cs b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClientOptions.cs index 49150efd..3d1e0287 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClientOptions.cs +++ b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/DownstreamApiClientOptions.cs @@ -17,9 +17,9 @@ public class DownstreamSourceOptions : ApiClientOptions public required bool AreProgressionsProvidedForIndexing { get; init; } - public required List? TenantTagsRequired { get; init; } + public required List TenantTagsRequired { get; init; } = []; - public required List? TenantTagsForbidden { get; init; } + public required List TenantTagsForbidden { get; init; } = []; public required List ExternalIdPrefixes { get; set; } } diff --git a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/HarmonyDB.Index.DownstreamApi.Client.csproj b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/HarmonyDB.Index.DownstreamApi.Client.csproj index 8638c4e4..67469338 100644 --- a/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/HarmonyDB.Index.DownstreamApi.Client.csproj +++ b/HarmonyDB.Index/HarmonyDB.Index.DownstreamApi.Client/HarmonyDB.Index.DownstreamApi.Client.csproj @@ -8,7 +8,7 @@ - + @@ -17,6 +17,7 @@ + diff --git a/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClient.cs b/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClient.cs index 7c5addde..4c1e1bc3 100644 --- a/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClient.cs +++ b/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClient.cs @@ -15,6 +15,16 @@ public AuthorizationApiClient(IHttpClientFactory httpClientFactory, IOptions CheckIdentityRespectingCode(Identity identity) + { + if (identity.Hash == _options.ServiceCode) + { + return null; + } + + return await CheckIdentity(identity); + } + public async Task CheckIdentity(Identity identity) { using var client = _httpClientFactory.CreateClient(); diff --git a/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClientOptions.cs b/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClientOptions.cs index d561e253..70a49477 100644 --- a/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClientOptions.cs +++ b/OneShelf.Authorization/OneShelf.Authorization.Api.Client/AuthorizationApiClientOptions.cs @@ -3,5 +3,8 @@ public class AuthorizationApiClientOptions { public Uri CheckIdentityEndpoint { get; set; } + public Uri PingEndpoint { get; set; } + + public string ServiceCode { get; set; } } \ No newline at end of file diff --git a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AnonymousFunctionBase.cs b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AnonymousFunctionBase.cs new file mode 100644 index 00000000..36db9365 --- /dev/null +++ b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AnonymousFunctionBase.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; + +namespace OneShelf.Common.Api.WithAuthorization; + +public abstract class AnonymousFunctionBase : FunctionBase +{ + private readonly SecurityContext _securityContext; + + protected AnonymousFunctionBase(ILoggerFactory loggerFactory, SecurityContext securityContext) + : base(loggerFactory) + { + _securityContext = securityContext; + } + + protected override void OnBeforeExecution() + { + base.OnBeforeExecution(); + _securityContext.InitAnonymous(); + } +} + +public abstract class AnonymousFunctionBase : FunctionBase +{ + private readonly SecurityContext _securityContext; + + protected AnonymousFunctionBase(ILoggerFactory loggerFactory, SecurityContext securityContext) : base(loggerFactory) + { + _securityContext = securityContext; + } + + protected override void OnBeforeExecution() + { + base.OnBeforeExecution(); + _securityContext.InitAnonymous(); + } +} \ No newline at end of file diff --git a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AuthorizationFunctionBase.cs b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AuthorizationFunctionBase.cs index d75176a6..e2c1821f 100644 --- a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AuthorizationFunctionBase.cs +++ b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/AuthorizationFunctionBase.cs @@ -10,15 +10,17 @@ public abstract class AuthorizationFunctionBase where TRequest : IRequestWithIdentity { private readonly AuthorizationApiClient _authorizationApiClient; + private readonly bool _respectServiceCode; protected readonly SecurityContext SecurityContext; protected readonly ILogger Logger; - protected AuthorizationFunctionBase(ILoggerFactory loggerFactory, AuthorizationApiClient authorizationApiClient, SecurityContext securityContext) + protected AuthorizationFunctionBase(ILoggerFactory loggerFactory, AuthorizationApiClient authorizationApiClient, SecurityContext securityContext, bool respectServiceCode = false) { Logger = loggerFactory.CreateLogger(GetType()); _authorizationApiClient = authorizationApiClient; SecurityContext = securityContext; + _respectServiceCode = respectServiceCode; } protected async Task RunHandler(HttpRequest httpRequest, TRequest request) @@ -27,10 +29,16 @@ protected async Task RunHandler(HttpRequest httpRequest, TRequest { Logger.LogInformation("C# HTTP trigger function processed a request."); - var authorizationCheckResult = await CheckAuthorization(request.Identity); + var authorizationCheckResult = _respectServiceCode ? await _authorizationApiClient.CheckIdentityRespectingCode(request.Identity) : await _authorizationApiClient.CheckIdentity(request.Identity); + if (authorizationCheckResult == null) + { + SecurityContext.InitService(); + return await ExecuteSuccessful(httpRequest, request); + } + if (authorizationCheckResult.IsSuccess) { - SecurityContext.InitSuccessful(authorizationCheckResult); + SecurityContext.InitSuccessful(request.Identity, authorizationCheckResult); return await ExecuteSuccessful(httpRequest, request); } @@ -47,11 +55,6 @@ protected async Task RunHandler(HttpRequest httpRequest, TRequest } } - protected virtual async Task CheckAuthorization(Identity identity) - { - return await _authorizationApiClient.CheckIdentity(identity); - } - protected abstract Task ExecuteSuccessful(HttpRequest httpRequest, TRequest request); protected virtual Task ExecuteFailed(string authorizationError) @@ -61,8 +64,8 @@ protected virtual Task ExecuteFailed(string authorizationError) public abstract class AuthorizationFunctionBase : AuthorizationFunctionBase where TRequest : IRequestWithIdentity { - protected AuthorizationFunctionBase(ILoggerFactory loggerFactory, AuthorizationApiClient authorizationApiClient, SecurityContext securityContext) - : base(loggerFactory, authorizationApiClient, securityContext) + protected AuthorizationFunctionBase(ILoggerFactory loggerFactory, AuthorizationApiClient authorizationApiClient, SecurityContext securityContext, bool respectServiceCode = false) + : base(loggerFactory, authorizationApiClient, securityContext, respectServiceCode) { } diff --git a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/OneShelf.Common.Api.WithAuthorization.csproj b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/OneShelf.Common.Api.WithAuthorization.csproj index f609d0c7..92c4c793 100644 --- a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/OneShelf.Common.Api.WithAuthorization.csproj +++ b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/OneShelf.Common.Api.WithAuthorization.csproj @@ -14,11 +14,12 @@ - + + diff --git a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/SecurityContext.cs b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/SecurityContext.cs index d6e98e5b..c89e4080 100644 --- a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/SecurityContext.cs +++ b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/SecurityContext.cs @@ -1,22 +1,90 @@ -using OneShelf.Authorization.Api.Model; +using Microsoft.Extensions.Options; +using OneShelf.Authorization.Api.Client; +using OneShelf.Authorization.Api.Model; namespace OneShelf.Common.Api.WithAuthorization; public class SecurityContext { + private readonly AuthorizationApiClientOptions _options; private CheckIdentityResponse? _checkIdentityResponse; + private Identity? _identity; - internal void InitSuccessful(CheckIdentityResponse checkIdentityResponse) + public const string ServiceTag = "Service"; + + public SecurityContext(IOptions options) + { + _options = options.Value; + } + + internal void InitSuccessful(Identity identity, CheckIdentityResponse checkIdentityResponse) { - if (checkIdentityResponse != null) + if (IsInitialized) throw new("Already initialized."); _checkIdentityResponse = checkIdentityResponse; + _identity = identity; + } + + public void InitAnonymous() + { + if (IsInitialized) + throw new("Already initialized."); + + IsAnonymous = true; + } + + public void InitService() + { + if (IsInitialized) + throw new("Already initialized."); + + IsService = true; } - public IReadOnlyList TenantTags => _checkIdentityResponse?.TenantTags ?? throw new("Not initialized"); + public bool IsAuthenticated => _identity != null; + + public bool IsAnonymous { get; private set; } + + public bool IsService { get; private set; } + + public bool IsInitialized => IsAuthenticated || IsAnonymous || IsService; + + public Identity Identity => _identity ?? throw new("Not initialized."); + + public Identity OutputIdentity + { + get + { + if (_identity != null) return _identity; + + if (IsService) + return new() + { + Hash = _options.ServiceCode, + }; + + throw new("Not initialized."); + } + } + + public IReadOnlyList TenantTags + { + get + { + if (IsAnonymous) return new List(); + + if (IsService) + return new List + { + ServiceTag, + }; + + return _checkIdentityResponse?.TenantTags ?? throw new("Not initialized."); + } + } - public int TenantId => _checkIdentityResponse?.TenantId ?? throw new("Not initialized"); + public int TenantId => _checkIdentityResponse?.TenantId ?? throw new("Not initialized."); - public bool ArePdfsAllowed => _checkIdentityResponse?.ArePdfsAllowed ?? throw new("Not initialized"); + public bool ArePdfsAllowed => _checkIdentityResponse?.ArePdfsAllowed ?? throw new("Not initialized."); } \ No newline at end of file diff --git a/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/ServiceFunctionBase.cs b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/ServiceFunctionBase.cs new file mode 100644 index 00000000..9cb0c66a --- /dev/null +++ b/OneShelf.Common/OneShelf.Common.Api.WithAuthorization/ServiceFunctionBase.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; + +namespace OneShelf.Common.Api.WithAuthorization; + +public abstract class ServiceFunctionBase : FunctionBase +{ + private readonly SecurityContext _securityContext; + + protected ServiceFunctionBase(ILoggerFactory loggerFactory, SecurityContext securityContext) + : base(loggerFactory) + { + _securityContext = securityContext; + } + + protected override void OnBeforeExecution() + { + base.OnBeforeExecution(); + _securityContext.InitService(); + } +} + +public abstract class ServiceFunctionBase : FunctionBase +{ + private readonly SecurityContext _securityContext; + + protected ServiceFunctionBase(ILoggerFactory loggerFactory, SecurityContext securityContext) : base(loggerFactory) + { + _securityContext = securityContext; + } + + protected override void OnBeforeExecution() + { + base.OnBeforeExecution(); + _securityContext.InitService(); + } +} \ No newline at end of file diff --git a/OneShelf.Common/OneShelf.Common.Api/FunctionBase.cs b/OneShelf.Common/OneShelf.Common.Api/FunctionBase.cs index 9011a389..03089119 100644 --- a/OneShelf.Common/OneShelf.Common.Api/FunctionBase.cs +++ b/OneShelf.Common/OneShelf.Common.Api/FunctionBase.cs @@ -17,7 +17,7 @@ protected async Task RunHandler(TRequest request) try { Logger.LogInformation("C# HTTP trigger function processed a request."); - + OnBeforeExecution(); return await ExecuteSuccessful(request); } catch (Exception e) @@ -27,6 +27,10 @@ protected async Task RunHandler(TRequest request) } } + protected virtual void OnBeforeExecution() + { + } + protected abstract Task ExecuteSuccessful(TRequest request); } diff --git a/OneShelf.Frontend/OneShelf.Frontend.Api/AuthorizationQuickCheck/AuthorizationQuickChecker.cs b/OneShelf.Frontend/OneShelf.Frontend.Api/AuthorizationQuickCheck/AuthorizationQuickChecker.cs index b79c8639..38d8bbcf 100644 --- a/OneShelf.Frontend/OneShelf.Frontend.Api/AuthorizationQuickCheck/AuthorizationQuickChecker.cs +++ b/OneShelf.Frontend/OneShelf.Frontend.Api/AuthorizationQuickCheck/AuthorizationQuickChecker.cs @@ -2,18 +2,23 @@ using System.Text; using System.Text.Json; using Microsoft.Extensions.Options; -using OneShelf.Authorization.Api.Model; +using OneShelf.Common.Api.WithAuthorization; using OneShelf.Frontend.Api.Model.V3.Api; namespace OneShelf.Frontend.Api.AuthorizationQuickCheck; public class AuthorizationQuickChecker { + private readonly SecurityContext _securityContext; private readonly AuthorizationQuickCheckOptions _options; - public AuthorizationQuickChecker(IOptions options) => _options = options.Value; + public AuthorizationQuickChecker(IOptions options, SecurityContext securityContext) + { + _securityContext = securityContext; + _options = options.Value; + } - public async Task CreateV1PreviewPdfLink(Identity identity, GetPdfsChunkRequestFile requestFile, + public async Task CreateV1PreviewPdfLink(GetPdfsChunkRequestFile requestFile, string urlAbsolutePathBase) { var expiration = DateTime.Now.AddMinutes(30).Ticks; @@ -22,15 +27,15 @@ public async Task CreateV1PreviewPdfLink(Identity identity, GetPdfsChunk { File = requestFile, Expiration = expiration, - UserId = identity.Id, - Hash = await Sign(identity.Id, JsonSerializer.Serialize(requestFile), expiration), + UserId = _securityContext.Identity.Id, + Hash = await Sign(JsonSerializer.Serialize(requestFile), expiration), }; return $"{urlAbsolutePathBase}{ApiUrls.V3PreviewPdf}?x={Uri.EscapeDataString(JsonSerializer.Serialize(request))}"; } - public async Task Sign(long userId, string file, long expiration) => - await Sign($"{userId}, {expiration}, {file}, {_options.Secret}"); + public async Task Sign(string file, long expiration) => + await Sign($"{_securityContext.Identity.Id}, {expiration}, {file}, {_options.Secret}"); private async Task Sign(string data) { diff --git a/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/GetPdfs.cs b/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/GetPdfs.cs index b53ffc1c..a2127dbf 100644 --- a/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/GetPdfs.cs +++ b/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/GetPdfs.cs @@ -182,7 +182,7 @@ private async Task> ExecuteChunk(Id { Data = await CompressionTools.DecompressToBytes(found.Data), PageCount = found.PageCount, - PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(identity, pdfsRequestFile, urlAbsolutePathBase), + PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(pdfsRequestFile, urlAbsolutePathBase), }; } else @@ -197,7 +197,7 @@ private async Task> ExecuteChunk(Id result[pdfsRequestFile.ExternalId] = new() { PageCount = found.Value, - PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(identity, pdfsRequestFile, urlAbsolutePathBase), + PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(pdfsRequestFile, urlAbsolutePathBase), }; } } @@ -227,7 +227,7 @@ private async Task> ExecuteChunk(Id { Data = includeData ? value.Result.pdf : null, PageCount = value.Result.pageCount, - PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(identity, requests[key], urlAbsolutePathBase), + PreviewLink = await _authorizationQuickChecker.CreateV1PreviewPdfLink(requests[key], urlAbsolutePathBase), }; } diff --git a/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/PreviewPdf.cs b/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/PreviewPdf.cs index 90476eb3..0fca380b 100644 --- a/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/PreviewPdf.cs +++ b/OneShelf.Frontend/OneShelf.Frontend.Api/Functions/V3/PreviewPdf.cs @@ -41,7 +41,7 @@ protected override async Task ExecuteSuccessful(PreviewPdfRequest { if (DateTime.Now > new DateTime(request.Expiration)) return new UnauthorizedObjectResult("Expired"); - var hash = await _authorizationQuickChecker.Sign(request.UserId, System.Text.Json.JsonSerializer.Serialize(request.File), request.Expiration); + var hash = await _authorizationQuickChecker.Sign(System.Text.Json.JsonSerializer.Serialize(request.File), request.Expiration); var isSuccess = request.Hash == hash; if (!isSuccess) return new UnauthorizedObjectResult("BadHash");