1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

PM-3925 - Tech Debt - Add missed tests for SSO Email 2FA Tokenable (#3270)

* PM-3925 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing  (2) Add missed tests to SsoEmail2faSessionTokenable

* PM-3925 - Take into account PR feedback
This commit is contained in:
Jared Snider 2023-09-15 14:54:52 -04:00 committed by GitHub
parent c08a48cd19
commit 227178980a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 178 additions and 4 deletions

View File

@ -6,11 +6,12 @@ namespace Bit.Core.Auth.Models.Business.Tokenables;
// This token just provides a verifiable authN mechanism for the API service
// TwoFactorController.cs SendEmailLogin anonymous endpoint so it cannot be
// used maliciously.
// used maliciously.
public class SsoEmail2faSessionTokenable : ExpiringTokenable
{
// Just over 2 min expiration (client expires session after 2 min)
private static readonly TimeSpan _tokenLifetime = TimeSpan.FromMinutes(2.05);
public static TimeSpan GetTokenLifetime() => TimeSpan.FromMinutes(2.05);
public const string ClearTextPrefix = "BwSsoEmail2FaSessionToken_";
public const string DataProtectorPurpose = "SsoEmail2faSessionTokenDataProtector";
@ -24,7 +25,7 @@ public class SsoEmail2faSessionTokenable : ExpiringTokenable
[JsonConstructor]
public SsoEmail2faSessionTokenable()
{
ExpirationDate = DateTime.UtcNow.Add(_tokenLifetime);
ExpirationDate = DateTime.UtcNow.Add(GetTokenLifetime());
}
public SsoEmail2faSessionTokenable(User user) : this()
@ -44,7 +45,7 @@ public class SsoEmail2faSessionTokenable : ExpiringTokenable
Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase);
}
// Validates deserialized
// Validates deserialized
protected override bool TokenIsValid() =>
Identifier == TokenIdentifier && Id != default && !string.IsNullOrWhiteSpace(Email);
}

View File

@ -0,0 +1,173 @@
using AutoFixture.Xunit2;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Entities;
using Bit.Core.Tokens;
using Xunit;
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
// Note: these test names follow MethodName_StateUnderTest_ExpectedBehavior pattern.
public class SsoEmail2faSessionTokenableTests
{
// Allow a small tolerance for possible execution delays or clock precision to avoid flaky tests.
private static readonly TimeSpan _timeTolerance = TimeSpan.FromMilliseconds(10);
/// <summary>
/// Tests the default constructor behavior when passed a null user.
/// </summary>
[Fact]
public void Constructor_NullUser_PropertiesSetToDefault()
{
var token = new SsoEmail2faSessionTokenable(null);
Assert.Equal(default, token.Id);
Assert.Equal(default, token.Email);
}
/// <summary>
/// Tests that when a valid user is provided to the constructor, the resulting token properties match the user.
/// </summary>
[Theory, AutoData]
public void Constructor_ValidUser_PropertiesSetFromUser(User user)
{
var token = new SsoEmail2faSessionTokenable(user);
Assert.Equal(user.Id, token.Id);
Assert.Equal(user.Email, token.Email);
}
/// <summary>
/// Tests the default expiration behavior immediately after initialization.
/// </summary>
[Fact]
public void Constructor_AfterInitialization_ExpirationSetToExpectedDuration()
{
var token = new SsoEmail2faSessionTokenable();
var expectedExpiration = DateTime.UtcNow + SsoEmail2faSessionTokenable.GetTokenLifetime();
Assert.True(expectedExpiration - token.ExpirationDate < _timeTolerance);
}
/// <summary>
/// Tests that a custom expiration date is preserved after token initialization.
/// </summary>
[Fact]
public void Constructor_CustomExpirationDate_ExpirationMatchesProvidedValue()
{
var customExpiration = DateTime.UtcNow.AddHours(3);
var token = new SsoEmail2faSessionTokenable
{
ExpirationDate = customExpiration
};
Assert.True((customExpiration - token.ExpirationDate).Duration() < _timeTolerance);
}
/// <summary>
/// Tests the validity of a token initialized with a null user.
/// </summary>
[Fact]
public void Valid_NullUser_ReturnsFalse()
{
var token = new SsoEmail2faSessionTokenable(null);
Assert.False(token.Valid);
}
/// <summary>
/// Tests the validity of a token with a non-matching identifier.
/// </summary>
[Theory, AutoData]
public void Valid_WrongIdentifier_ReturnsFalse(User user)
{
var token = new SsoEmail2faSessionTokenable(user)
{
Identifier = "not correct"
};
Assert.False(token.Valid);
}
/// <summary>
/// Tests the token validity when user ID is null.
/// </summary>
[Theory, AutoData]
public void TokenIsValid_NullUserId_ReturnsFalse(User user)
{
user.Id = default; // Guid.Empty
var token = new SsoEmail2faSessionTokenable(user);
Assert.False(token.TokenIsValid(user));
}
/// <summary>
/// Tests the token validity when user's email is null.
/// </summary>
[Theory, AutoData]
public void TokenIsValid_NullEmail_ReturnsFalse(User user)
{
user.Email = null;
var token = new SsoEmail2faSessionTokenable(user);
Assert.False(token.TokenIsValid(user));
}
/// <summary>
/// Tests the token validity when user ID and email match the token properties.
/// </summary>
[Theory, AutoData]
public void TokenIsValid_MatchingUserIdAndEmail_ReturnsTrue(User user)
{
var token = new SsoEmail2faSessionTokenable(user);
Assert.True(token.TokenIsValid(user));
}
/// <summary>
/// Ensures that the token is invalid when the provided user's ID doesn't match the token's ID.
/// </summary>
[Theory, AutoData]
public void TokenIsValid_WrongUserId_ReturnsFalse(User user)
{
// Given a token initialized with a user's details
var token = new SsoEmail2faSessionTokenable(user);
// modify the user's ID
user.Id = Guid.NewGuid();
// Then the token should be considered invalid
Assert.False(token.TokenIsValid(user));
}
/// <summary>
/// Ensures that the token is invalid when the provided user's email doesn't match the token's email.
/// </summary>
[Theory, AutoData]
public void TokenIsValid_WrongEmail_ReturnsFalse(User user)
{
// Given a token initialized with a user's details
var token = new SsoEmail2faSessionTokenable(user);
// modify the user's email
user.Email = "nonMatchingEmail@example.com";
// Then the token should be considered invalid
Assert.False(token.TokenIsValid(user));
}
/// <summary>
/// Tests the deserialization of a token to ensure that the expiration date is preserved.
/// </summary>
[Theory, AutoData]
public void FromToken_SerializedToken_PreservesExpirationDate(User user)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new SsoEmail2faSessionTokenable(user)
{
ExpirationDate = expectedDateTime
};
var result = Tokenable.FromToken<SsoEmail2faSessionTokenable>(token.ToToken());
Assert.Equal(expectedDateTime, result.ExpirationDate, precision: _timeTolerance);
}
}