diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index 08b24282d1..a8e4e5c67e 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -64,7 +64,9 @@ namespace Bit.Api.Controllers } var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId); - var responses = organizationUsers.Select(o => new OrganizationUserUserDetailsResponseModel(o)); + var responseTasks = organizationUsers.Select(async o => new OrganizationUserUserDetailsResponseModel(o, + await _userService.TwoFactorIsEnabledAsync(o))); + var responses = await Task.WhenAll(responseTasks); return new ListResponseModel(responses); } @@ -175,7 +177,7 @@ namespace Bit.Api.Controllers { throw new BadRequestException("Only owners can update other owners."); } - + await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g))); } diff --git a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs index 22db217627..dcb2236abd 100644 --- a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs @@ -61,7 +61,7 @@ namespace Bit.Core.Models.Api public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponseModel { public OrganizationUserUserDetailsResponseModel(OrganizationUserUserDetails organizationUser, - string obj = "organizationUserUserDetails") + bool twoFactorEnabled, string obj = "organizationUserUserDetails") : base(organizationUser, obj) { if(organizationUser == null) @@ -71,9 +71,11 @@ namespace Bit.Core.Models.Api Name = organizationUser.Name; Email = organizationUser.Email; + TwoFactorEnabled = twoFactorEnabled; } public string Name { get; set; } public string Email { get; set; } + public bool TwoFactorEnabled { get; set; } } } diff --git a/src/Core/Models/Data/OrganizationUserUserDetails.cs b/src/Core/Models/Data/OrganizationUserUserDetails.cs index 897b82ebd8..be341d4764 100644 --- a/src/Core/Models/Data/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/OrganizationUserUserDetails.cs @@ -1,17 +1,58 @@ using System; +using System.Collections.Generic; +using Bit.Core.Enums; +using Newtonsoft.Json; namespace Bit.Core.Models.Data { - public class OrganizationUserUserDetails : IExternal + public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser { + private Dictionary _twoFactorProviders; + public Guid Id { get; set; } public Guid OrganizationId { get; set; } public Guid? UserId { get; set; } public string Name { get; set; } public string Email { get; set; } - public Enums.OrganizationUserStatusType Status { get; set; } - public Enums.OrganizationUserType Type { get; set; } + public string TwoFactorProviders { get; set; } + public bool? Premium { get; set; } + public OrganizationUserStatusType Status { get; set; } + public OrganizationUserType Type { get; set; } public bool AccessAll { get; set; } public string ExternalId { get; set; } + + public Dictionary GetTwoFactorProviders() + { + if(string.IsNullOrWhiteSpace(TwoFactorProviders)) + { + return null; + } + + try + { + if(_twoFactorProviders == null) + { + _twoFactorProviders = + JsonConvert.DeserializeObject>( + TwoFactorProviders); + } + + return _twoFactorProviders; + } + catch(JsonSerializationException) + { + return null; + } + } + + public Guid? GetUserId() + { + return UserId; + } + + public bool GetPremium() + { + return Premium.GetValueOrDefault(false); + } } } diff --git a/src/Core/Models/ITwoFactorProvidersUser.cs b/src/Core/Models/ITwoFactorProvidersUser.cs new file mode 100644 index 0000000000..e79ae0b1d8 --- /dev/null +++ b/src/Core/Models/ITwoFactorProvidersUser.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using Bit.Core.Enums; + +namespace Bit.Core.Models +{ + public interface ITwoFactorProvidersUser + { + string TwoFactorProviders { get; } + Dictionary GetTwoFactorProviders(); + Guid? GetUserId(); + bool GetPremium(); + } +} diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index cffa45add3..ce0a73319e 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -6,11 +6,10 @@ using Newtonsoft.Json; using Bit.Core.Services; using Bit.Core.Exceptions; using Microsoft.AspNetCore.Identity; -using System.Threading.Tasks; namespace Bit.Core.Models.Table { - public class User : ITableObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable + public class User : ITableObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable, ITwoFactorProvidersUser { private Dictionary _twoFactorProviders; @@ -83,6 +82,16 @@ namespace Bit.Core.Models.Table } } + public Guid? GetUserId() + { + return Id; + } + + public bool GetPremium() + { + return Premium; + } + public void SetTwoFactorProviders(Dictionary providers) { TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index a64d24d895..cb763065b0 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -54,8 +54,8 @@ namespace Bit.Core.Services Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); Task GenerateLicenseAsync(User user, BillingInfo billingInfo = null); Task CheckPasswordAsync(User user, string password); - Task CanAccessPremium(User user); - Task TwoFactorIsEnabledAsync(User user); - Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, User user); + Task CanAccessPremium(ITwoFactorProvidersUser user); + Task TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user); + Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user); } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 0ebf0daf65..fb6c46921a 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -900,13 +900,18 @@ namespace Bit.Core.Services return success; } - public async Task CanAccessPremium(User user) + public async Task CanAccessPremium(ITwoFactorProvidersUser user) { - if(user.Premium) + var userId = user.GetUserId(); + if(!userId.HasValue) + { + return false; + } + if(user.GetPremium()) { return true; } - var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id); + var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, userId.Value); if(!orgs.Any()) { return false; @@ -916,7 +921,7 @@ namespace Bit.Core.Services orgAbilities[o.Id].UsersGetPremium && orgAbilities[o.Id].Enabled); } - public async Task TwoFactorIsEnabledAsync(User user) + public async Task TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user) { var providers = user.GetTwoFactorProviders(); if(providers == null) @@ -941,7 +946,7 @@ namespace Bit.Core.Services return false; } - public async Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, User user) + public async Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user) { var providers = user.GetTwoFactorProviders(); if(providers == null || !providers.ContainsKey(provider) || !providers[provider].Enabled) diff --git a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql index c8df6b6661..476ac71a39 100644 --- a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql @@ -6,6 +6,8 @@ SELECT OU.[OrganizationId], U.[Name], ISNULL(U.[Email], OU.[Email]) Email, + U.[TwoFactorProviders], + U.[Premium], OU.[Status], OU.[Type], OU.[AccessAll], diff --git a/util/Setup/DbScripts/2018-12-19_00_OrgUserTwoFactorEnabled.sql b/util/Setup/DbScripts/2018-12-19_00_OrgUserTwoFactorEnabled.sql new file mode 100644 index 0000000000..d5a5b2b678 --- /dev/null +++ b/util/Setup/DbScripts/2018-12-19_00_OrgUserTwoFactorEnabled.sql @@ -0,0 +1,25 @@ +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserUserDetailsView') +BEGIN + DROP VIEW [dbo].[OrganizationUserUserDetailsView] +END +GO + +CREATE VIEW [dbo].[OrganizationUserUserDetailsView] +AS +SELECT + OU.[Id], + OU.[UserId], + OU.[OrganizationId], + U.[Name], + ISNULL(U.[Email], OU.[Email]) Email, + U.[TwoFactorProviders], + U.[Premium], + OU.[Status], + OU.[Type], + OU.[AccessAll], + OU.[ExternalId] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] +GO diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index 1b780b6340..d48a268697 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -15,6 +15,7 @@ +