mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 23:52:50 -05:00
[PM-1188] Server owner auth migration (#2825)
* [PM-1188] add sso project to auth * [PM-1188] move sso api models to auth * [PM-1188] fix sso api model namespace & imports * [PM-1188] move core files to auth * [PM-1188] fix core sso namespace & models * [PM-1188] move sso repository files to auth * [PM-1188] fix sso repo files namespace & imports * [PM-1188] move sso sql files to auth folder * [PM-1188] move sso test files to auth folders * [PM-1188] fix sso tests namespace & imports * [PM-1188] move auth api files to auth folder * [PM-1188] fix auth api files namespace & imports * [PM-1188] move auth core files to auth folder * [PM-1188] fix auth core files namespace & imports * [PM-1188] move auth email templates to auth folder * [PM-1188] move auth email folder back into shared directory * [PM-1188] fix auth email names * [PM-1188] move auth core models to auth folder * [PM-1188] fix auth model namespace & imports * [PM-1188] add entire Identity project to auth codeowners * [PM-1188] fix auth orm files namespace & imports * [PM-1188] move auth orm files to auth folder * [PM-1188] move auth sql files to auth folder * [PM-1188] move auth tests to auth folder * [PM-1188] fix auth test files namespace & imports * [PM-1188] move emergency access api files to auth folder * [PM-1188] fix emergencyaccess api files namespace & imports * [PM-1188] move emergency access core files to auth folder * [PM-1188] fix emergency access core files namespace & imports * [PM-1188] move emergency access orm files to auth folder * [PM-1188] fix emergency access orm files namespace & imports * [PM-1188] move emergency access sql files to auth folder * [PM-1188] move emergencyaccess test files to auth folder * [PM-1188] fix emergency access test files namespace & imports * [PM-1188] move captcha files to auth folder * [PM-1188] fix captcha files namespace & imports * [PM-1188] move auth admin files into auth folder * [PM-1188] fix admin auth files namespace & imports - configure mvc to look in auth folders for views * [PM-1188] remove extra imports and formatting * [PM-1188] fix ef auth model imports * [PM-1188] fix DatabaseContextModelSnapshot paths * [PM-1188] fix grant import in ef * [PM-1188] update sqlproj * [PM-1188] move missed sqlproj files * [PM-1188] move auth ef models out of auth folder * [PM-1188] fix auth ef models namespace * [PM-1188] remove auth ef models unused imports * [PM-1188] fix imports for auth ef models * [PM-1188] fix more ef model imports * [PM-1188] fix file encodings
This commit is contained in:
@ -0,0 +1,38 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
public class AuthenticationTokenProviderTests : BaseTokenProviderTests<AuthenticatorTokenProvider>
|
||||
{
|
||||
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Authenticator;
|
||||
|
||||
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
(
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["Key"] = "stuff",
|
||||
},
|
||||
true
|
||||
),
|
||||
(
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["Key"] = ""
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
|
||||
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<AuthenticatorTokenProvider> sutProvider)
|
||||
{
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
}
|
92
test/Core.Test/Auth/Identity/BaseTokenProviderTests.cs
Normal file
92
test/Core.Test/Auth/Identity/BaseTokenProviderTests.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public abstract class BaseTokenProviderTests<T>
|
||||
where T : IUserTwoFactorTokenProvider<User>
|
||||
{
|
||||
public abstract TwoFactorProviderType TwoFactorProviderType { get; }
|
||||
|
||||
#region Helpers
|
||||
protected static IEnumerable<object[]> SetupCanGenerateData(params (Dictionary<string, object> MetaData, bool ExpectedResponse)[] data)
|
||||
{
|
||||
return data.Select(d =>
|
||||
new object[]
|
||||
{
|
||||
d.MetaData,
|
||||
d.ExpectedResponse,
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual IUserService AdditionalSetup(SutProvider<T> sutProvider, User user)
|
||||
{
|
||||
var userService = Substitute.For<IUserService>();
|
||||
|
||||
sutProvider.GetDependency<IServiceProvider>()
|
||||
.GetService(typeof(IUserService))
|
||||
.Returns(userService);
|
||||
|
||||
SetupUserService(userService, user);
|
||||
|
||||
return userService;
|
||||
}
|
||||
|
||||
protected virtual void SetupUserService(IUserService userService, User user)
|
||||
{
|
||||
userService
|
||||
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
protected static UserManager<User> SubstituteUserManager()
|
||||
{
|
||||
return new UserManager<User>(Substitute.For<IUserStore<User>>(),
|
||||
Substitute.For<IOptions<IdentityOptions>>(),
|
||||
Substitute.For<IPasswordHasher<User>>(),
|
||||
Enumerable.Empty<IUserValidator<User>>(),
|
||||
Enumerable.Empty<IPasswordValidator<User>>(),
|
||||
Substitute.For<ILookupNormalizer>(),
|
||||
Substitute.For<IdentityErrorDescriber>(),
|
||||
Substitute.For<IServiceProvider>(),
|
||||
Substitute.For<ILogger<UserManager<User>>>());
|
||||
}
|
||||
|
||||
protected void MockDatabase(User user, Dictionary<string, object> metaData)
|
||||
{
|
||||
var providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType] = new TwoFactorProvider
|
||||
{
|
||||
Enabled = true,
|
||||
MetaData = metaData,
|
||||
},
|
||||
};
|
||||
|
||||
user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<T> sutProvider)
|
||||
{
|
||||
var userManager = SubstituteUserManager();
|
||||
MockDatabase(user, metaData);
|
||||
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user);
|
||||
Assert.Equal(expectedResponse, response);
|
||||
}
|
||||
}
|
45
test/Core.Test/Auth/Identity/EmailTokenProviderTests.cs
Normal file
45
test/Core.Test/Auth/Identity/EmailTokenProviderTests.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
public class EmailTokenProviderTests : BaseTokenProviderTests<EmailTokenProvider>
|
||||
{
|
||||
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Email;
|
||||
|
||||
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
(
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["Email"] = "test@email.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
(
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["NotEmail"] = "value",
|
||||
},
|
||||
false
|
||||
),
|
||||
(
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["Email"] = "",
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
|
||||
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<EmailTokenProvider> sutProvider)
|
||||
{
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
}
|
93
test/Core.Test/Auth/IdentityServer/TokenRetrievalTests.cs
Normal file
93
test/Core.Test/Auth/IdentityServer/TokenRetrievalTests.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using Bit.Core.Auth.IdentityServer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.IdentityServer;
|
||||
|
||||
public class TokenRetrievalTests
|
||||
{
|
||||
private readonly Func<HttpRequest, string> _sut = TokenRetrieval.FromAuthorizationHeaderOrQueryString();
|
||||
|
||||
[Fact]
|
||||
public void RetrieveToken_FromHeader_ReturnsToken()
|
||||
{
|
||||
// Arrange
|
||||
var headers = new HeaderDictionary
|
||||
{
|
||||
{ "Authorization", "Bearer test_value" },
|
||||
{ "X-Test-Header", "random_value" }
|
||||
};
|
||||
|
||||
var request = Substitute.For<HttpRequest>();
|
||||
|
||||
request.Headers.Returns(headers);
|
||||
|
||||
// Act
|
||||
var token = _sut(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test_value", token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetrieveToken_FromQueryString_ReturnsToken()
|
||||
{
|
||||
// Arrange
|
||||
var queryString = new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "access_token", "test_value" },
|
||||
{ "test-query", "random_value" }
|
||||
};
|
||||
|
||||
var request = Substitute.For<HttpRequest>();
|
||||
request.Query.Returns(new QueryCollection(queryString));
|
||||
|
||||
// Act
|
||||
var token = _sut(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test_value", token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetrieveToken_HasBoth_ReturnsHeaderToken()
|
||||
{
|
||||
// Arrange
|
||||
var queryString = new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "access_token", "query_string_token" },
|
||||
{ "test-query", "random_value" }
|
||||
};
|
||||
|
||||
var headers = new HeaderDictionary
|
||||
{
|
||||
{ "Authorization", "Bearer header_token" },
|
||||
{ "X-Test-Header", "random_value" }
|
||||
};
|
||||
|
||||
var request = Substitute.For<HttpRequest>();
|
||||
request.Headers.Returns(headers);
|
||||
request.Query.Returns(new QueryCollection(queryString));
|
||||
|
||||
// Act
|
||||
var token = _sut(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("header_token", token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetrieveToken_NoToken_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var request = Substitute.For<HttpRequest>();
|
||||
|
||||
// Act
|
||||
var token = _sut(request);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Tokens;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class EmergencyAccessInviteTokenableTests
|
||||
{
|
||||
[Theory, AutoData]
|
||||
public void SerializationSetsCorrectDateTime(EmergencyAccess emergencyAccess)
|
||||
{
|
||||
var token = new EmergencyAccessInviteTokenable(emergencyAccess, 2);
|
||||
Assert.Equal(Tokenable.FromToken<EmergencyAccessInviteTokenable>(token.ToToken().ToString()).ExpirationDate,
|
||||
token.ExpirationDate,
|
||||
TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInvalidIfIdentifierIsWrong()
|
||||
{
|
||||
var token = new EmergencyAccessInviteTokenable(DateTime.MaxValue)
|
||||
{
|
||||
Email = "email",
|
||||
Id = Guid.NewGuid(),
|
||||
Identifier = "not correct"
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class HCaptchaTokenableTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanHandleNullUser()
|
||||
{
|
||||
var token = new HCaptchaTokenable(null);
|
||||
|
||||
Assert.Equal(default, token.Id);
|
||||
Assert.Equal(default, token.Email);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TokenWithNullUserIsInvalid()
|
||||
{
|
||||
var token = new HCaptchaTokenable(null)
|
||||
{
|
||||
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TokenValidityCheckNullUserIdIsInvalid(User user)
|
||||
{
|
||||
var token = new HCaptchaTokenable(user)
|
||||
{
|
||||
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
|
||||
};
|
||||
|
||||
Assert.False(token.TokenIsValid(null));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void CanUpdateExpirationToNonStandard(User user)
|
||||
{
|
||||
var token = new HCaptchaTokenable(user)
|
||||
{
|
||||
ExpirationDate = DateTime.MinValue
|
||||
};
|
||||
|
||||
Assert.Equal(DateTime.MinValue, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void SetsDataFromUser(User user)
|
||||
{
|
||||
var token = new HCaptchaTokenable(user);
|
||||
|
||||
Assert.Equal(user.Id, token.Id);
|
||||
Assert.Equal(user.Email, token.Email);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void SerializationSetsCorrectDateTime(User user)
|
||||
{
|
||||
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
|
||||
var token = new HCaptchaTokenable(user)
|
||||
{
|
||||
ExpirationDate = expectedDateTime
|
||||
};
|
||||
|
||||
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
|
||||
|
||||
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void IsInvalidIfIdentifierIsWrong(User user)
|
||||
{
|
||||
var token = new HCaptchaTokenable(user)
|
||||
{
|
||||
Identifier = "not correct"
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class SsoTokenableTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanHandleNullOrganization()
|
||||
{
|
||||
var token = new SsoTokenable(null, default);
|
||||
|
||||
Assert.Equal(default, token.OrganizationId);
|
||||
Assert.Equal(default, token.DomainHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TokenWithNullOrganizationIsInvalid()
|
||||
{
|
||||
var token = new SsoTokenable(null, 500)
|
||||
{
|
||||
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TokenValidityCheckNullOrganizationIsInvalid(Organization organization)
|
||||
{
|
||||
var token = new SsoTokenable(organization, 500)
|
||||
{
|
||||
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
|
||||
};
|
||||
|
||||
Assert.False(token.TokenIsValid(null));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void SetsDataFromOrganization(Organization organization)
|
||||
{
|
||||
var token = new SsoTokenable(organization, default);
|
||||
|
||||
Assert.Equal(organization.Id, token.OrganizationId);
|
||||
Assert.Equal(organization.Identifier, token.DomainHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsExpirationFromConstructor()
|
||||
{
|
||||
var expectedDateTime = DateTime.UtcNow.AddSeconds(500);
|
||||
var token = new SsoTokenable(null, 500);
|
||||
|
||||
Assert.Equal(expectedDateTime, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void SerializationSetsCorrectDateTime(Organization organization)
|
||||
{
|
||||
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
|
||||
var token = new SsoTokenable(organization, default)
|
||||
{
|
||||
ExpirationDate = expectedDateTime
|
||||
};
|
||||
|
||||
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
|
||||
|
||||
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public void TokenIsValidFailsWhenExpired(Organization organization)
|
||||
{
|
||||
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
|
||||
var token = new SsoTokenable(organization, default)
|
||||
{
|
||||
ExpirationDate = expectedDateTime
|
||||
};
|
||||
|
||||
var result = token.TokenIsValid(organization);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
171
test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs
Normal file
171
test/Core.Test/Auth/Services/EmergencyAccessServiceTests.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class EmergencyAccessServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_PremiumCannotUpdate(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
||||
{
|
||||
savingUser.Premium = false;
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
GrantorId = savingUser.Id,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(savingUser.Id).Returns(savingUser);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
|
||||
|
||||
Assert.Contains("Not a premium user.", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InviteAsync_UserWithKeyConnectorCannotUseTakeover(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User invitingUser, string email, int waitTime)
|
||||
{
|
||||
invitingUser.UsesKeyConnector = true;
|
||||
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteAsync(invitingUser, email, EmergencyAccessType.Takeover, waitTime));
|
||||
|
||||
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User confirmingUser, string key)
|
||||
{
|
||||
confirmingUser.UsesKeyConnector = true;
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
Status = EmergencyAccessStatusType.Accepted,
|
||||
GrantorId = confirmingUser.Id,
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(confirmingUser.Id).Returns(confirmingUser);
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.ConfirmUserAsync(new Guid(), key, confirmingUser.Id));
|
||||
|
||||
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_UserWithKeyConnectorCannotUseTakeover(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
|
||||
{
|
||||
savingUser.UsesKeyConnector = true;
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
GrantorId = savingUser.Id,
|
||||
};
|
||||
|
||||
var userService = sutProvider.GetDependency<IUserService>();
|
||||
userService.GetUserByIdAsync(savingUser.Id).Returns(savingUser);
|
||||
userService.CanAccessPremium(savingUser).Returns(true);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
|
||||
|
||||
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
|
||||
{
|
||||
grantor.UsesKeyConnector = true;
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
Status = EmergencyAccessStatusType.Confirmed,
|
||||
GranteeId = initiatingUser.Id,
|
||||
GrantorId = grantor.Id,
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
|
||||
|
||||
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TakeoverAsync_UserWithKeyConnectorCannotUseTakeover(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
|
||||
{
|
||||
grantor.UsesKeyConnector = true;
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantor.Id,
|
||||
GranteeId = requestingUser.Id,
|
||||
Status = EmergencyAccessStatusType.RecoveryApproved,
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.TakeoverAsync(new Guid(), requestingUser));
|
||||
|
||||
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PasswordAsync_Disables_2FA_Providers_On_The_Grantor(
|
||||
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
|
||||
{
|
||||
grantor.UsesKeyConnector = true;
|
||||
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new TwoFactorProvider
|
||||
{
|
||||
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
|
||||
Enabled = true
|
||||
}
|
||||
});
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
GrantorId = grantor.Id,
|
||||
GranteeId = requestingUser.Id,
|
||||
Status = EmergencyAccessStatusType.RecoveryApproved,
|
||||
Type = EmergencyAccessType.Takeover,
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
|
||||
|
||||
await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey");
|
||||
|
||||
Assert.Empty(grantor.GetTwoFactorProviders());
|
||||
await sutProvider.GetDependency<IUserRepository>().Received().ReplaceAsync(grantor);
|
||||
}
|
||||
}
|
319
test/Core.Test/Auth/Services/SsoConfigServiceTests.cs
Normal file
319
test/Core.Test/Auth/Services/SsoConfigServiceTests.cs
Normal file
@ -0,0 +1,319 @@
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SsoConfigServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingItem_UpdatesRevisionDateOnly(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = 1,
|
||||
Data = "{}",
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.UpsertAsync(ssoConfig).Returns(Task.CompletedTask);
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().Received()
|
||||
.UpsertAsync(ssoConfig);
|
||||
|
||||
Assert.Equal(utcNow.AddDays(-10), ssoConfig.CreationDate);
|
||||
Assert.True(ssoConfig.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_NewItem_UpdatesCreationAndRevisionDate(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = "{}",
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.UpsertAsync(ssoConfig).Returns(Task.CompletedTask);
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().Received()
|
||||
.UpsertAsync(ssoConfig);
|
||||
|
||||
Assert.True(ssoConfig.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(ssoConfig.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_PreventDisablingKeyConnector(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var oldSsoConfig = new SsoConfig
|
||||
{
|
||||
Id = 1,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
var newSsoConfig = new SsoConfig
|
||||
{
|
||||
Id = 1,
|
||||
Data = "{}",
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow,
|
||||
};
|
||||
|
||||
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
|
||||
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig);
|
||||
ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(organization.Id)
|
||||
.Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = true } });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(newSsoConfig, organization));
|
||||
|
||||
Assert.Contains("Key Connector cannot be disabled at this moment.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_AllowDisablingKeyConnectorWhenNoUserIsUsingIt(
|
||||
SutProvider<SsoConfigService> sutProvider, Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var oldSsoConfig = new SsoConfig
|
||||
{
|
||||
Id = 1,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
var newSsoConfig = new SsoConfig
|
||||
{
|
||||
Id = 1,
|
||||
Data = "{}",
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow,
|
||||
};
|
||||
|
||||
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
|
||||
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig);
|
||||
ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(organization.Id)
|
||||
.Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = false } });
|
||||
|
||||
await sutProvider.Sut.SaveAsync(newSsoConfig, organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled_Throws(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(ssoConfig, organization));
|
||||
|
||||
Assert.Contains("Key Connector requires the Single Organization policy to be enabled.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_KeyConnector_SsoPolicyNotEnabled_Throws(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(
|
||||
Arg.Any<Guid>(), Enums.PolicyType.SingleOrg).Returns(new Policy
|
||||
{
|
||||
Enabled = true
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(ssoConfig, organization));
|
||||
|
||||
Assert.Contains("Key Connector requires the Single Sign-On Authentication policy to be enabled.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_KeyConnector_SsoConfigNotEnabled_Throws(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = false,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(
|
||||
Arg.Any<Guid>(), Arg.Any<Enums.PolicyType>()).Returns(new Policy
|
||||
{
|
||||
Enabled = true
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(ssoConfig, organization));
|
||||
|
||||
Assert.Contains("You must enable SSO to use Key Connector.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_KeyConnector_KeyConnectorAbilityNotEnabled_Throws(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
organization.UseKeyConnector = false;
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(
|
||||
Arg.Any<Guid>(), Arg.Any<Enums.PolicyType>()).Returns(new Policy
|
||||
{
|
||||
Enabled = true,
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(ssoConfig, organization));
|
||||
|
||||
Assert.Contains("Organization cannot use Key Connector.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_KeyConnector_Success(SutProvider<SsoConfigService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
organization.UseKeyConnector = true;
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
KeyConnectorEnabled = true,
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = utcNow.AddDays(-10),
|
||||
RevisionDate = utcNow.AddDays(-10),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(
|
||||
Arg.Any<Guid>(), Arg.Any<Enums.PolicyType>()).Returns(new Policy
|
||||
{
|
||||
Enabled = true,
|
||||
});
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user