From 751c30deddbe33956f93c215780a4eb7de29a482 Mon Sep 17 00:00:00 2001 From: Artemy <109195690+stalcker2288969@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:07:05 +0700 Subject: [PATCH] Add CurrentUserSessionService for session ID retrieval Introduces ICurrentUserSessionService and its implementation to provide access to the current user's session ID via the 'sid' claim. Registers the service in DI and adds comprehensive unit tests to validate correct and error scenarios. --- .../ConfigurationProgramExtensions.cs | 1 + .../Extensions/CurrentUserServiceTests.cs | 3 +- .../CurrentUserSessionServiceTests.cs | 109 ++++++++++++++++++ .../Extensions/CurrentUserSessionService.cs | 27 +++++ .../Extensions/ICurrentUserSessionService.cs | 6 + 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 Govor.Application.Tests/Infrastructure/Extensions/CurrentUserSessionServiceTests.cs create mode 100644 Govor.Application/Infrastructure/Extensions/CurrentUserSessionService.cs create mode 100644 Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserSessionService.cs diff --git a/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs index 4795c58..58c622f 100644 --- a/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs @@ -64,6 +64,7 @@ public static class ConfigurationProgramExtensions services.AddHttpContextAccessor(); // it's very important for CurrentUserService services.AddScoped(); + services.AddScoped(); services.AddMemoryCache(); services.AddScoped(); diff --git a/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserServiceTests.cs b/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserServiceTests.cs index 69b8fbc..31fc66b 100644 --- a/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserServiceTests.cs +++ b/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserServiceTests.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using Govor.Application.Infrastructure.Extensions; +using Govor.Application.Interfaces.Infrastructure.Extensions; using Microsoft.AspNetCore.Http; using Moq; @@ -9,7 +10,7 @@ namespace Govor.Application.Tests.Infrastructure.Extensions; public class CurrentUserServiceTests { private Mock _httpContextAccessorMock; - private CurrentUserService _currentUserService; + private ICurrentUserService _currentUserService; [SetUp] public void SetUp() diff --git a/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserSessionServiceTests.cs b/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserSessionServiceTests.cs new file mode 100644 index 0000000..c9c42e5 --- /dev/null +++ b/Govor.Application.Tests/Infrastructure/Extensions/CurrentUserSessionServiceTests.cs @@ -0,0 +1,109 @@ +using System.Security.Claims; +using Govor.Application.Infrastructure.Extensions; +using Govor.Application.Interfaces.Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; +using Moq; + +namespace Govor.Application.Tests.Infrastructure.Extensions; + +[TestFixture] +[TestOf(typeof(CurrentUserSessionService))] +public class CurrentUserSessionServiceTests +{ + private Mock _httpContextAccessorMock; + private ICurrentUserSessionService _sessionService; + + [SetUp] + public void SetUp() + { + _httpContextAccessorMock = new Mock(); + _sessionService = new CurrentUserSessionService(_httpContextAccessorMock.Object); + } + + [Test] + public void GetCurrentSessionId_ValidSidClaim_ReturnsGuid() + { + // Arrange + var sid = Guid.NewGuid(); + var claims = new[] { new Claim("sid", sid.ToString()) }; + var identity = new ClaimsIdentity(claims); + var principal = new ClaimsPrincipal(identity); + + var httpContextMock = new Mock(); + httpContextMock.Setup(x => x.User).Returns(principal); + + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); + + // Act + var result = _sessionService.GetUserSessionId(); + + // Assert + Assert.That(result, Is.EqualTo(sid)); + } + + [Test] + public void GetCurrentSessionId_NoHttpContext_ThrowsUnauthorizedAccessException() + { + // Arrange + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns((HttpContext)null); + + // Act & Assert + var ex = Assert.Throws(() => _sessionService.GetUserSessionId()); + Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid")); + } + + + [Test] + public void GetCurrentSessionId_NoSidlaim_ThrowsUnauthorizedAccessException() + { + // Arrange + var claims = new[] { new Claim("otherClaim", "value") }; + var identity = new ClaimsIdentity(claims); + var principal = new ClaimsPrincipal(identity); + + var httpContextMock = new Mock(); + httpContextMock.Setup(x => x.User).Returns(principal); + + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); + + // Act & Assert + var ex = Assert.Throws(() => _sessionService.GetUserSessionId()); + Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid")); + } + + [Test] + public void GetUserSessionId_InvalidSidValueClaim_ThrowsUnauthorizedAccessException() + { + // Arrange + var claims = new[] { new Claim("sid", "invalid-guid") }; + var identity = new ClaimsIdentity(claims); + var principal = new ClaimsPrincipal(identity); + + var httpContextMock = new Mock(); + httpContextMock.Setup(x => x.User).Returns(principal); + + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); + + // Act & Assert + var ex = Assert.Throws(() => _sessionService.GetUserSessionId()); + Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid")); + } + + [Test] + public void GetUserSessionId_EmptySidValueClaim_ThrowsUnauthorizedAccessException() + { + // Arrange + var claims = new[] { new Claim("sid", "") }; + var identity = new ClaimsIdentity(claims); + var principal = new ClaimsPrincipal(identity); + + var httpContextMock = new Mock(); + httpContextMock.Setup(x => x.User).Returns(principal); + + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); + + // Act & Assert + var ex = Assert.Throws(() => _sessionService.GetUserSessionId()); + Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid")); + } +} \ No newline at end of file diff --git a/Govor.Application/Infrastructure/Extensions/CurrentUserSessionService.cs b/Govor.Application/Infrastructure/Extensions/CurrentUserSessionService.cs new file mode 100644 index 0000000..f986ab4 --- /dev/null +++ b/Govor.Application/Infrastructure/Extensions/CurrentUserSessionService.cs @@ -0,0 +1,27 @@ +using Govor.Application.Interfaces.Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Govor.Application.Infrastructure.Extensions; + +public class CurrentUserSessionService : ICurrentUserSessionService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public CurrentUserSessionService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Guid GetUserSessionId() + { + var user = _httpContextAccessor.HttpContext?.User; + var userIdClaim = user?.FindFirst("sid")?.Value; + + if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId)) + { + throw new UnauthorizedAccessException("Session id (sid) claim is missing or invalid"); + } + + return userId; + } +} \ No newline at end of file diff --git a/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserSessionService.cs b/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserSessionService.cs new file mode 100644 index 0000000..f4000a9 --- /dev/null +++ b/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserSessionService.cs @@ -0,0 +1,6 @@ +namespace Govor.Application.Interfaces.Infrastructure.Extensions; + +public interface ICurrentUserSessionService +{ + Guid GetUserSessionId(); +} \ No newline at end of file