Skip to content

Commit 2a7b4b7

Browse files
Updated Redis session configuration to use subdomain as instance name, ensuring tenant-specific session isolation in a multi-tenant environment
(cherry picked from commit cc3e010)
1 parent e6b0e2d commit 2a7b4b7

File tree

3 files changed

+106
-7
lines changed

3 files changed

+106
-7
lines changed

dotnet/src/dotnetcore/GxClasses/Services/Session/GXSessionFactory.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ public class GXSessionServiceFactory
1212
{
1313
private static readonly IGXLogger log = GXLoggerFactory.GetLogger<GXSessionServiceFactory>();
1414

15+
static ISessionService sessionService;
1516
static string REDIS = "REDIS";
1617
static string DATABASE = "DATABASE";
1718
public static ISessionService GetProvider()
1819
{
19-
ISessionService sessionService = null;
20+
if (sessionService != null)
21+
return sessionService;
2022
GXService providerService = GXServices.Instance?.Get(GXServices.SESSION_SERVICE);
2123
if (providerService != null)
2224
{
@@ -63,6 +65,7 @@ public class GxRedisSession : ISessionService
6365
internal static string SESSION_INSTANCE = "SESSION_PROVIDER_INSTANCE_NAME";
6466
internal static string SESSION_PASSWORD = "SESSION_PROVIDER_PASSWORD";
6567
static string SESSION_TIMEOUT = "SESSION_PROVIDER_SESSION_TIMEOUT";
68+
const string SUBDOMAIN = "%SUBDOMAIN%";
6669
public GxRedisSession(GXService serviceProvider)
6770
{
6871
string password = serviceProvider.Properties.Get(SESSION_PASSWORD);
@@ -100,6 +103,10 @@ public GxRedisSession(string host, string password, string instanceName, int ses
100103
GXLogging.Debug(log, "Redis Host:", host, ", InstanceName:", instanceName);
101104
GXLogging.Debug(log, "Redis sessionTimeoutMinutes:", sessionTimeout.ToString());
102105
}
106+
internal bool IsMultitenant
107+
{
108+
get { return InstanceName == SUBDOMAIN; }
109+
}
103110
public string ConnectionString { get; }
104111
public string InstanceName { get; }
105112
public int SessionTimeout { get; }
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.Extensions.Caching.Distributed;
3+
using Microsoft.Extensions.Caching.StackExchangeRedis;
4+
using System.Collections.Concurrent;
5+
using System.Threading.Tasks;
6+
using System.Threading;
7+
using System;
8+
using GeneXus.Services;
9+
using System.Linq;
10+
11+
namespace GeneXus.Application
12+
{
13+
public class TenantRedisCache : IDistributedCache
14+
{
15+
private readonly IHttpContextAccessor _httpContextAccessor;
16+
private readonly IServiceProvider _serviceProvider;
17+
private readonly ConcurrentDictionary<string, RedisCache> _redisCaches = new();
18+
19+
public TenantRedisCache(IHttpContextAccessor httpContextAccessor, IServiceProvider serviceProvider)
20+
{
21+
_httpContextAccessor = httpContextAccessor;
22+
_serviceProvider = serviceProvider;
23+
}
24+
25+
private IDistributedCache GetTenantCache()
26+
{
27+
string tenantId = _httpContextAccessor.HttpContext?.Items[TenantMiddleware.TENANT_ID]?.ToString() ?? "default";
28+
29+
return _redisCaches.GetOrAdd(tenantId, id =>
30+
{
31+
ISessionService sessionService = GXSessionServiceFactory.GetProvider();
32+
var options = new RedisCacheOptions
33+
{
34+
Configuration = sessionService.ConnectionString,
35+
InstanceName = $"{id}:"
36+
};
37+
return new RedisCache(options);
38+
});
39+
}
40+
41+
public byte[] Get(string key) => GetTenantCache().Get(key);
42+
public Task<byte[]> GetAsync(string key, CancellationToken token = default) => GetTenantCache().GetAsync(key, token);
43+
public void Refresh(string key) => GetTenantCache().Refresh(key);
44+
public Task RefreshAsync(string key, CancellationToken token = default) => GetTenantCache().RefreshAsync(key, token);
45+
public void Remove(string key) => GetTenantCache().Remove(key);
46+
public Task RemoveAsync(string key, CancellationToken token = default) => GetTenantCache().RemoveAsync(key, token);
47+
public void Set(string key, byte[] value, DistributedCacheEntryOptions options) => GetTenantCache().Set(key, value, options);
48+
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default) => GetTenantCache().SetAsync(key, value, options, token);
49+
}
50+
51+
52+
public class TenantMiddleware
53+
{
54+
internal const string TENANT_ID = "TenantId";
55+
private readonly RequestDelegate _next;
56+
57+
public TenantMiddleware(RequestDelegate next)
58+
{
59+
_next = next;
60+
}
61+
62+
public async Task Invoke(HttpContext context)
63+
{
64+
string host = context.Request.Host.Host;
65+
string subdomain = host.Split('.').FirstOrDefault();
66+
context.Items[TENANT_ID] = subdomain;
67+
68+
await _next(context);
69+
}
70+
}
71+
}

dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using Microsoft.AspNetCore.Routing;
2626
using Microsoft.AspNetCore.Server.Kestrel.Core;
2727
using Microsoft.AspNetCore.StaticFiles;
28+
using Microsoft.Extensions.Caching.Distributed;
2829
using Microsoft.Extensions.Configuration;
2930
using Microsoft.Extensions.DependencyInjection;
3031
using Microsoft.Extensions.FileProviders;
@@ -302,15 +303,27 @@ private void DefineCorsPolicy(IServiceCollection services)
302303

303304
private void ConfigureSessionService(IServiceCollection services, ISessionService sessionService)
304305
{
306+
305307
if (sessionService is GxRedisSession)
306308
{
307-
services.AddStackExchangeRedisCache(options =>
309+
GxRedisSession gxRedisSession = (GxRedisSession)sessionService;
310+
if (gxRedisSession.IsMultitenant)
308311
{
309-
GXLogging.Info(log, $"Using Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
310-
options.Configuration = sessionService.ConnectionString;
311-
options.InstanceName = sessionService.InstanceName;
312-
});
313-
services.AddDataProtection().PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(sessionService.ConnectionString), DATA_PROTECTION_KEYS).SetApplicationName(sessionService.InstanceName);
312+
GXLogging.Info(log, $"Using multi-tenant Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
313+
314+
services.AddSingleton<IDistributedCache, TenantRedisCache>();
315+
services.AddDataProtection().PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(sessionService.ConnectionString), DATA_PROTECTION_KEYS).SetApplicationName("default");
316+
}
317+
else
318+
{
319+
services.AddStackExchangeRedisCache(options =>
320+
{
321+
GXLogging.Info(log, $"Using Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
322+
options.Configuration = sessionService.ConnectionString;
323+
options.InstanceName = sessionService.InstanceName;
324+
});
325+
services.AddDataProtection().PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(sessionService.ConnectionString), DATA_PROTECTION_KEYS).SetApplicationName(sessionService.InstanceName);
326+
}
314327
}
315328
else if (sessionService is GxDatabaseSession)
316329
{
@@ -356,6 +369,14 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
356369
app.UseCookiePolicy();
357370
app.UseSession();
358371
app.UseStaticFiles();
372+
373+
ISessionService sessionService = GXSessionServiceFactory.GetProvider();
374+
GxRedisSession gxRedisSession = sessionService as GxRedisSession;
375+
if (gxRedisSession != null && gxRedisSession.IsMultitenant)
376+
{
377+
app.UseMiddleware<TenantMiddleware>();
378+
}
379+
359380
ConfigureCors(app);
360381
ConfigureSwaggerUI(app, baseVirtualPath);
361382

0 commit comments

Comments
 (0)