From 2b97c17f68e6c0bbdede743460de6e6522f000cb Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:48:43 +0300 Subject: [PATCH 1/6] Multi-tenant messaging: filter out events from unknown tenants --- .../NBB.Messaging.MultiTenancy.csproj | 1 + .../TenantMiddleware.cs | 59 +++++++++++++++++-- .../CachedTenantRepositoryDecorator.cs | 20 +++++++ .../ConfigurationTenantRepository.cs | 10 ++++ .../Repositories/ITenantRepository.cs | 1 + 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/NBB.Messaging.MultiTenancy.csproj b/src/Messaging/NBB.Messaging.MultiTenancy/NBB.Messaging.MultiTenancy.csproj index 1db817d0..5dbd641f 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/NBB.Messaging.MultiTenancy.csproj +++ b/src/Messaging/NBB.Messaging.MultiTenancy/NBB.Messaging.MultiTenancy.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs index 5f4614ab..9943f16c 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs +++ b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs @@ -13,6 +13,9 @@ using NBB.MultiTenancy.Identification.Services; using NBB.MultiTenancy.Abstractions; using System.Diagnostics; +using Microsoft.Extensions.Logging; +using MediatR; +using System.Linq; namespace NBB.Messaging.MultiTenancy { @@ -27,13 +30,15 @@ public class TenantMiddleware : IPipelineMiddleware private readonly ITenantIdentificationService _tenantIdentificationService; private readonly IOptions _tenancyOptions; private readonly ITenantRepository _tenantRepository; + private readonly ILogger _logger; - public TenantMiddleware(ITenantContextAccessor tenantContextAccessor, ITenantIdentificationService tenantIdentificationService, IOptions tenancyOptions, ITenantRepository tenantRepository) + public TenantMiddleware(ITenantContextAccessor tenantContextAccessor, ITenantIdentificationService tenantIdentificationService, IOptions tenancyOptions, ITenantRepository tenantRepository, ILogger logger) { _tenantContextAccessor = tenantContextAccessor; _tenantIdentificationService = tenantIdentificationService; _tenancyOptions = tenancyOptions; _tenantRepository = tenantRepository; + _logger = logger; } public async Task Invoke(MessagingContext context, CancellationToken cancellationToken, Func next) @@ -45,21 +50,63 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio if (_tenancyOptions.Value.TenancyType == TenancyType.MonoTenant) { - _tenantContextAccessor.TenantContext = new TenantContext(Tenant.Default); + _tenantContextAccessor.TenantContext = new TenantContext(Tenant.Default); await next(); return; } + Tenant tenant; + + if (context.MessagingEnvelope.Payload is INotification) + { + tenant = await TryLoadTenant(context.TopicName, cancellationToken); + if (tenant == null) + { + return; + } + } + else + { + tenant = await LoadTenant(cancellationToken); + } + + + _tenantContextAccessor.TenantContext = new TenantContext(tenant); + + Activity.Current?.SetTag(TracingTags.TenantId, tenant.TenantId); + + await next(); + } + + private async Task LoadTenant(CancellationToken cancellationToken) + { var tenantId = await _tenantIdentificationService.GetTenantIdAsync(); var tenant = await _tenantRepository.Get(tenantId, cancellationToken) - ?? throw new ApplicationException($"Tenant {tenantId} not found"); + ?? throw new ApplicationException($"Tenant {tenantId} not found"); - _tenantContextAccessor.TenantContext = new TenantContext(tenant); + return tenant; + } - Activity.Current?.SetTag(TracingTags.TenantId, tenantId); - await next(); + private async Task TryLoadTenant(string topic, CancellationToken cancellationToken) + { + var tenantId = await _tenantIdentificationService.TryGetTenantIdAsync(); + if (!tenantId.HasValue) + { + _logger.LogDebug("Tenant could not be identified. Message {Topic} will be ignored.", topic); + return null; + } + + var tenant = await _tenantRepository.TryGet(tenantId.Value, cancellationToken); + if (tenant == null) + { + _logger.LogDebug("Tenant {Tenant} not found or not enabled. Message {Topic} will be ignored.", tenantId.Value, topic); + return null; + } + + return tenant; } + } diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs index d9716f41..c2dc92a3 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs @@ -43,6 +43,26 @@ public async Task Get(Guid id, CancellationToken token) return dbTenant; } + public async Task TryGet(Guid id, CancellationToken token) + { + var cacheKey = CacheTenantByIdKey(id); + var cachedTenant = await GetTenantFromCache(cacheKey, token); + if (cachedTenant != null) + { + return cachedTenant; + } + + var dbTenant = await _tenantRepository.TryGet(id, token); + if (dbTenant == null) + { + return null; + } + + await SetTenantToCache(dbTenant, cacheKey, token); + + return dbTenant; + } + private async Task GetTenantFromCache(string key, CancellationToken token = default) { var sTenant = await _cache.GetStringAsync(key, token); diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs index 73c69efe..1ccfaf95 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs @@ -54,6 +54,16 @@ public Task Get(Guid id, CancellationToken token = default) return Task.FromResult(result.Enabled ? result : throw new Exception($"Tenant {result.Code} is disabled ")); } + public Task TryGet(Guid id, CancellationToken token = default) + { + if (!tenantMap.TryGetValue(id, out var result) || !result.Enabled) + { + return Task.FromResult(default(Tenant)); + } + + return Task.FromResult(result); + } + public Task GetByHost(string host, CancellationToken token = default) { throw new NotImplementedException(); diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs index 9293e0e1..b405c7a1 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs @@ -11,6 +11,7 @@ namespace NBB.MultiTenancy.Abstractions.Repositories public interface ITenantRepository { Task Get(Guid id, CancellationToken token = default); + Task TryGet(Guid id, CancellationToken token = default); Task GetByHost(string host, CancellationToken token = default); Task> GetAll(CancellationToken token = default); } From 696185c716ea13b058111859b7f8e7105f99da6b Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:04:11 +0300 Subject: [PATCH 2/6] Removed get from interface --- .../CachedTenantRepositoryDecorator.cs | 20 ------------------- .../ConfigurationTenantRepository.cs | 11 ---------- .../Repositories/ITenantRepository.cs | 13 +++++++++++- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs index c2dc92a3..8c5aadec 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/CachedTenantRepositoryDecorator.cs @@ -23,26 +23,6 @@ public CachedTenantRepositoryDecorator(ITenantRepository tenantRepository, IDist _cache = cache; } - public async Task Get(Guid id, CancellationToken token) - { - var cacheKey = CacheTenantByIdKey(id); - var cachedTenant = await GetTenantFromCache(cacheKey, token); - if (cachedTenant != null) - { - return cachedTenant; - } - - var dbTenant = await _tenantRepository.Get(id, token); - if (dbTenant == null) - { - return null; - } - - await SetTenantToCache(dbTenant, cacheKey, token); - - return dbTenant; - } - public async Task TryGet(Guid id, CancellationToken token) { var cacheKey = CacheTenantByIdKey(id); diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs index 1ccfaf95..b1830014 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ConfigurationTenantRepository.cs @@ -43,17 +43,6 @@ public ConfigurationTenantRepository(IConfiguration configuration, IOptions Get(Guid id, CancellationToken token = default) - { - if (!tenantMap.TryGetValue(id, out var result)) - { - throw new TenantNotFoundException(id); - } - - - return Task.FromResult(result.Enabled ? result : throw new Exception($"Tenant {result.Code} is disabled ")); - } - public Task TryGet(Guid id, CancellationToken token = default) { if (!tenantMap.TryGetValue(id, out var result) || !result.Enabled) diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs index b405c7a1..ae53257d 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs @@ -10,9 +10,20 @@ namespace NBB.MultiTenancy.Abstractions.Repositories { public interface ITenantRepository { - Task Get(Guid id, CancellationToken token = default); Task TryGet(Guid id, CancellationToken token = default); Task GetByHost(string host, CancellationToken token = default); Task> GetAll(CancellationToken token = default); + + async Task Get(Guid id, CancellationToken token = default) + { + var result = await TryGet(id, token); + if (result == null) + { + throw new TenantNotFoundException(id); + } + + + return result.Enabled ? result : throw new Exception($"Tenant {result.Code} is disabled "); + } } } From 86927d8cef2498b9b7a1861a99c17eca1aafe347 Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:05:45 +0300 Subject: [PATCH 3/6] Whitespace --- .../Repositories/ITenantRepository.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs index ae53257d..015248a6 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs @@ -22,7 +22,6 @@ async Task Get(Guid id, CancellationToken token = default) throw new TenantNotFoundException(id); } - return result.Enabled ? result : throw new Exception($"Tenant {result.Code} is disabled "); } } From 6e5f140a944db65104d6d350103278ae8b9e7dec Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:25:52 +0300 Subject: [PATCH 4/6] Primary constructor --- .../TenantMiddleware.cs | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs index 9943f16c..bb89f519 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs +++ b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs @@ -24,33 +24,24 @@ namespace NBB.Messaging.MultiTenancy /// obtained from the current identification strategy and builds the tenant context. /// /// - public class TenantMiddleware : IPipelineMiddleware + public class TenantMiddleware( + ITenantContextAccessor tenantContextAccessor, + ITenantIdentificationService tenantIdentificationService, + IOptions tenancyOptions, + ITenantRepository tenantRepository, + ILogger logger + ) : IPipelineMiddleware { - private readonly ITenantContextAccessor _tenantContextAccessor; - private readonly ITenantIdentificationService _tenantIdentificationService; - private readonly IOptions _tenancyOptions; - private readonly ITenantRepository _tenantRepository; - private readonly ILogger _logger; - - public TenantMiddleware(ITenantContextAccessor tenantContextAccessor, ITenantIdentificationService tenantIdentificationService, IOptions tenancyOptions, ITenantRepository tenantRepository, ILogger logger) - { - _tenantContextAccessor = tenantContextAccessor; - _tenantIdentificationService = tenantIdentificationService; - _tenancyOptions = tenancyOptions; - _tenantRepository = tenantRepository; - _logger = logger; - } - public async Task Invoke(MessagingContext context, CancellationToken cancellationToken, Func next) { - if (_tenantContextAccessor.TenantContext != null) + if (tenantContextAccessor.TenantContext != null) { throw new ApplicationException("Tenant context is already set"); } - if (_tenancyOptions.Value.TenancyType == TenancyType.MonoTenant) + if (tenancyOptions.Value.TenancyType == TenancyType.MonoTenant) { - _tenantContextAccessor.TenantContext = new TenantContext(Tenant.Default); + tenantContextAccessor.TenantContext = new TenantContext(Tenant.Default); await next(); return; } @@ -71,7 +62,7 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio } - _tenantContextAccessor.TenantContext = new TenantContext(tenant); + tenantContextAccessor.TenantContext = new TenantContext(tenant); Activity.Current?.SetTag(TracingTags.TenantId, tenant.TenantId); @@ -80,8 +71,8 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio private async Task LoadTenant(CancellationToken cancellationToken) { - var tenantId = await _tenantIdentificationService.GetTenantIdAsync(); - var tenant = await _tenantRepository.Get(tenantId, cancellationToken) + var tenantId = await tenantIdentificationService.GetTenantIdAsync(); + var tenant = await tenantRepository.Get(tenantId, cancellationToken) ?? throw new ApplicationException($"Tenant {tenantId} not found"); return tenant; @@ -90,17 +81,17 @@ private async Task LoadTenant(CancellationToken cancellationToken) private async Task TryLoadTenant(string topic, CancellationToken cancellationToken) { - var tenantId = await _tenantIdentificationService.TryGetTenantIdAsync(); + var tenantId = await tenantIdentificationService.TryGetTenantIdAsync(); if (!tenantId.HasValue) { - _logger.LogDebug("Tenant could not be identified. Message {Topic} will be ignored.", topic); + logger.LogDebug("Tenant could not be identified. Message {Topic} will be ignored.", topic); return null; } - var tenant = await _tenantRepository.TryGet(tenantId.Value, cancellationToken); + var tenant = await tenantRepository.TryGet(tenantId.Value, cancellationToken); if (tenant == null) { - _logger.LogDebug("Tenant {Tenant} not found or not enabled. Message {Topic} will be ignored.", tenantId.Value, topic); + logger.LogDebug("Tenant {Tenant} not found or not enabled. Message {Topic} will be ignored.", tenantId.Value, topic); return null; } From fb2eb9881460b375755535e3691a6ea16ac4cb53 Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:45:16 +0300 Subject: [PATCH 5/6] fixed test --- .../ConfigurationTenantRepositoryTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs b/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs index a4602ca5..ba7be638 100644 --- a/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs +++ b/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs @@ -190,7 +190,7 @@ public async Task get_tenant_should_throw_for_disabled_tenant() var options = new OptionsWrapper(tenancyHostingOptions); - var repo = new ConfigurationTenantRepository(configuration, options); + ITenantRepository repo = new ConfigurationTenantRepository(configuration, options); //Act Func action = async() => @@ -232,7 +232,7 @@ public async Task get_should_bind_tenant_code_from_section_name() var repo = new ConfigurationTenantRepository(configuration, options); //arrange - var actual = await repo.Get(System.Guid.Parse("ef8d5362-9969-4e02-8794-0d1af56816f6")); + var actual = await repo.TryGet(Guid.Parse("ef8d5362-9969-4e02-8794-0d1af56816f6")); // Assert actual.Should().NotBeNull(); From f3b7802edd6531b1772f82f9c8660f825b5b7fb5 Mon Sep 17 00:00:00 2001 From: fraliv13 <5892139+fraliv13@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:01:05 +0300 Subject: [PATCH 6/6] Fix tests --- .../NBB.Messaging.MultiTenancy/TenantMiddleware.cs | 2 +- .../Repositories/ITenantRepository.cs | 10 +--------- .../ConfigurationTenantRepositoryTests.cs | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs index bb89f519..9dfd32f0 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs +++ b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs @@ -31,7 +31,7 @@ public class TenantMiddleware( ITenantRepository tenantRepository, ILogger logger ) : IPipelineMiddleware - { + { public async Task Invoke(MessagingContext context, CancellationToken cancellationToken, Func next) { if (tenantContextAccessor.TenantContext != null) diff --git a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs index 015248a6..0ebf09d1 100644 --- a/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs +++ b/src/MultiTenancy/NBB.MultiTenancy.Abstractions/Repositories/ITenantRepository.cs @@ -15,14 +15,6 @@ public interface ITenantRepository Task> GetAll(CancellationToken token = default); async Task Get(Guid id, CancellationToken token = default) - { - var result = await TryGet(id, token); - if (result == null) - { - throw new TenantNotFoundException(id); - } - - return result.Enabled ? result : throw new Exception($"Tenant {result.Code} is disabled "); - } + => await TryGet(id, token) ?? throw new TenantNotFoundException(id); } } diff --git a/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs b/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs index ba7be638..b9b8456c 100644 --- a/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs +++ b/test/UnitTests/MultiTenancy/NBB.MultiTenancy.Configuration.Tests/ConfigurationTenantRepositoryTests.cs @@ -197,7 +197,7 @@ public async Task get_tenant_should_throw_for_disabled_tenant() await repo.Get(Guid.Parse(tenantId)); //Assert - await action.Should().ThrowAsync().WithMessage("*disabled*"); + await action.Should().ThrowAsync(); } [Fact]