mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[Provider] Setup provider (#1378)
This commit is contained in:
@ -9,6 +9,14 @@
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition="!$(DefineConstants.Contains('OSS'))">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\bitwarden_license\src\CommCore\CommCore.csproj" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
@ -85,7 +85,7 @@ namespace Bit.Admin.Controllers
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var users = await _providerUserRepository.GetManyByProviderAsync(id);
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderViewModel(provider, users));
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ namespace Bit.Admin.Controllers
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var users = await _providerUserRepository.GetManyByProviderAsync(id);
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderEditModel(provider, users));
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public class ProviderEditModel : ProviderViewModel
|
||||
{
|
||||
public ProviderEditModel(Provider provider, IEnumerable<ProviderUser> providerUsers)
|
||||
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
|
||||
: base(provider, providerUsers)
|
||||
{
|
||||
Name = provider.Name;
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public class ProviderViewModel
|
||||
{
|
||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUser> providerUsers)
|
||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
|
||||
{
|
||||
Provider = provider;
|
||||
UserCount = providerUsers.Count();
|
||||
|
@ -13,6 +13,10 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Stripe;
|
||||
|
||||
#if !OSS
|
||||
using Bit.CommCore.Utilities;
|
||||
#endif
|
||||
|
||||
namespace Bit.Admin
|
||||
{
|
||||
public class Startup
|
||||
@ -65,6 +69,12 @@ namespace Bit.Admin
|
||||
// Services
|
||||
services.AddBaseServices();
|
||||
services.AddDefaultServices(globalSettings);
|
||||
|
||||
#if OSS
|
||||
services.AddOosServices();
|
||||
#else
|
||||
services.AddCommCoreServices();
|
||||
#endif
|
||||
|
||||
// Mvc
|
||||
services.AddMvc(config =>
|
||||
|
@ -19,6 +19,14 @@
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition="!$(DefineConstants.Contains('OSS'))">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\bitwarden_license\src\CommCore\CommCore.csproj" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.6" />
|
||||
<PackageReference Include="NewRelic.Agent" Version="8.30.0" />
|
||||
|
@ -18,6 +18,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums.Provider;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -30,6 +31,7 @@ namespace Bit.Api.Controllers
|
||||
private readonly IFolderRepository _folderRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
@ -40,6 +42,7 @@ namespace Bit.Api.Controllers
|
||||
IFolderRepository folderRepository,
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IPaymentService paymentService,
|
||||
ISsoUserRepository ssoUserRepository,
|
||||
IUserRepository userRepository,
|
||||
@ -50,6 +53,7 @@ namespace Bit.Api.Controllers
|
||||
_globalSettings = globalSettings;
|
||||
_organizationService = organizationService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_paymentService = paymentService;
|
||||
_userRepository = userRepository;
|
||||
_userService = userService;
|
||||
@ -358,7 +362,9 @@ namespace Bit.Api.Controllers
|
||||
|
||||
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
var response = new ProfileResponseModel(user, organizationUserDetails,
|
||||
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
|
||||
ProviderUserStatusType.Confirmed);
|
||||
var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
|
||||
await _userService.TwoFactorIsEnabledAsync(user));
|
||||
return response;
|
||||
}
|
||||
@ -384,7 +390,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
await _userService.SaveUserAsync(model.ToUser(user));
|
||||
var response = new ProfileResponseModel(user, null, await _userService.TwoFactorIsEnabledAsync(user));
|
||||
var response = new ProfileResponseModel(user, null, null, await _userService.TwoFactorIsEnabledAsync(user));
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -535,7 +541,7 @@ namespace Bit.Api.Controllers
|
||||
BillingAddressCountry = model.Country,
|
||||
BillingAddressPostalCode = model.PostalCode,
|
||||
});
|
||||
var profile = new ProfileResponseModel(user, null, await _userService.TwoFactorIsEnabledAsync(user));
|
||||
var profile = new ProfileResponseModel(user, null, null, await _userService.TwoFactorIsEnabledAsync(user));
|
||||
return new PaymentResponseModel
|
||||
{
|
||||
UserProfile = profile,
|
||||
|
62
src/Api/Controllers/ProviderOrganizationsController.cs
Normal file
62
src/Api/Controllers/ProviderOrganizationsController.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
[Route("providers/{providerId:guid}/organizations")]
|
||||
[Authorize("Application")]
|
||||
public class ProviderOrganizationsController : Controller
|
||||
{
|
||||
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public ProviderOrganizationsController(
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderService providerService,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerService = providerService;
|
||||
_userService = userService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>> Get(Guid providerId)
|
||||
{
|
||||
if (!_currentContext.AccessProviderOrganizations(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId);
|
||||
var responses = providerOrganizations.Select(o => new ProviderOrganizationOrganizationDetailsResponseModel(o));
|
||||
return new ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPost("add")]
|
||||
public async Task Add(Guid providerId, [FromBody]ProviderOrganizationAddRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderOrganizations(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
|
||||
await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key);
|
||||
}
|
||||
}
|
||||
}
|
205
src/Api/Controllers/ProviderUsersController.cs
Normal file
205
src/Api/Controllers/ProviderUsersController.cs
Normal file
@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Models.Business.Provider;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
[Route("providers/{providerId:guid}/users")]
|
||||
[Authorize("Application")]
|
||||
public class ProviderUsersController : Controller
|
||||
{
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public ProviderUsersController(
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IProviderService providerService,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerService = providerService;
|
||||
_userService = userService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ProviderUserResponseModel> Get(Guid providerId, Guid id)
|
||||
{
|
||||
var providerUser = await _providerUserRepository.GetByIdAsync(id);
|
||||
if (providerUser == null || !_currentContext.ManageProviderUsers(providerUser.ProviderId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new ProviderUserResponseModel(providerUser);
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<ProviderUserUserDetailsResponseModel>> Get(Guid providerId)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var providerUsers = await _providerUserRepository.GetManyDetailsByProviderAsync(providerId);
|
||||
var responses = providerUsers.Select(o => new ProviderUserUserDetailsResponseModel(o));
|
||||
return new ListResponseModel<ProviderUserUserDetailsResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPost("invite")]
|
||||
public async Task Invite(Guid providerId, [FromBody]ProviderUserInviteRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _providerService.InviteUserAsync(providerId, userId.Value, new ProviderUserInvite(model));
|
||||
}
|
||||
|
||||
[HttpPost("reinvite")]
|
||||
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkReinvite(Guid providerId, [FromBody]ProviderUserBulkRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var result = await _providerService.ResendInvitesAsync(providerId, userId.Value, model.Ids);
|
||||
return new ListResponseModel<ProviderUserBulkResponseModel>(
|
||||
result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2)));
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/reinvite")]
|
||||
public async Task Reinvite(Guid providerId, Guid id)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _providerService.ResendInvitesAsync(providerId, userId.Value, new [] { id });
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/accept")]
|
||||
public async Task Accept(Guid providerId, Guid id, [FromBody]ProviderUserAcceptRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
await _providerService.AcceptUserAsync(id, user, model.Token);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/confirm")]
|
||||
public async Task Confirm(Guid providerId, Guid id, [FromBody]ProviderUserConfirmRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _providerService.ConfirmUsersAsync(providerId, new Dictionary<Guid, string> { [id] = model.Key }, userId.Value);
|
||||
}
|
||||
|
||||
[HttpPost("confirm")]
|
||||
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkConfirm(Guid providerId,
|
||||
[FromBody]ProviderUserBulkConfirmRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var results = await _providerService.ConfirmUsersAsync(providerId, model.ToDictionary(), userId.Value);
|
||||
|
||||
return new ListResponseModel<ProviderUserBulkResponseModel>(results.Select(r =>
|
||||
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
||||
}
|
||||
|
||||
[HttpPost("public-keys")]
|
||||
public async Task<ListResponseModel<ProviderUserPublicKeyResponseModel>> UserPublicKeys(Guid providerId, [FromBody]ProviderUserBulkRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _providerUserRepository.GetManyPublicKeysByProviderUserAsync(providerId, model.Ids);
|
||||
var responses = result.Select(r => new ProviderUserPublicKeyResponseModel(r.Id, r.PublicKey)).ToList();
|
||||
return new ListResponseModel<ProviderUserPublicKeyResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[HttpPost("{id:guid}")]
|
||||
public async Task Put(Guid providerId, Guid id, [FromBody]ProviderUserUpdateRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var providerUser = await _providerUserRepository.GetByIdAsync(id);
|
||||
if (providerUser == null || providerUser.ProviderId != providerId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _providerService.SaveUserAsync(model.ToProviderUser(providerUser), userId.Value);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[HttpPost("{id:guid}/delete")]
|
||||
public async Task Delete(Guid providerId, Guid id)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _providerService.DeleteUsersAsync(providerId, new [] { id }, userId.Value);
|
||||
}
|
||||
|
||||
[HttpDelete("")]
|
||||
[HttpPost("delete")]
|
||||
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkDelete(Guid providerId, [FromBody]ProviderUserBulkRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ManageProviderUsers(providerId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var result = await _providerService.DeleteUsersAsync(providerId, model.Ids, userId.Value);
|
||||
return new ListResponseModel<ProviderUserBulkResponseModel>(result.Select(r =>
|
||||
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
||||
}
|
||||
}
|
||||
}
|
70
src/Api/Controllers/ProvidersController.cs
Normal file
70
src/Api/Controllers/ProvidersController.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
[Route("providers")]
|
||||
[Authorize("Application")]
|
||||
public class ProvidersController : Controller
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public ProvidersController(IUserService userService, IProviderRepository providerRepository,
|
||||
IProviderService providerService, ICurrentContext currentContext)
|
||||
{
|
||||
_userService = userService;
|
||||
_providerRepository = providerRepository;
|
||||
_providerService = providerService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ProviderResponseModel> Get(Guid id)
|
||||
{
|
||||
if (!_currentContext.ProviderUser(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new ProviderResponseModel(provider);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/setup")]
|
||||
public async Task<ProviderResponseModel> Setup(Guid id, [FromBody]ProviderSetupRequestModel model)
|
||||
{
|
||||
if (!_currentContext.ProviderProviderAdmin(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
|
||||
var response =
|
||||
await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key);
|
||||
|
||||
return new ProviderResponseModel(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ using Bit.Core.Exceptions;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Settings;
|
||||
|
||||
@ -25,6 +26,7 @@ namespace Bit.Api.Controllers
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
@ -36,6 +38,7 @@ namespace Bit.Api.Controllers
|
||||
ICollectionRepository collectionRepository,
|
||||
ICollectionCipherRepository collectionCipherRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
ISendRepository sendRepository,
|
||||
GlobalSettings globalSettings)
|
||||
@ -46,6 +49,7 @@ namespace Bit.Api.Controllers
|
||||
_collectionRepository = collectionRepository;
|
||||
_collectionCipherRepository = collectionCipherRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_policyRepository = policyRepository;
|
||||
_sendRepository = sendRepository;
|
||||
_globalSettings = globalSettings;
|
||||
@ -62,6 +66,8 @@ namespace Bit.Api.Controllers
|
||||
|
||||
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
|
||||
ProviderUserStatusType.Confirmed);
|
||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, hasEnabledOrgs);
|
||||
@ -80,7 +86,8 @@ namespace Bit.Api.Controllers
|
||||
|
||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, organizationUserDetails,
|
||||
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends);
|
||||
providerUserDetails, folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains,
|
||||
policies, sends);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,10 @@ using Microsoft.OpenApi.Models;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
#if !OSS
|
||||
using Bit.CommCore.Utilities;
|
||||
#endif
|
||||
|
||||
namespace Bit.Api
|
||||
{
|
||||
public class Startup
|
||||
@ -119,6 +123,12 @@ namespace Bit.Api
|
||||
services.AddDefaultServices(globalSettings);
|
||||
services.AddCoreLocalizationServices();
|
||||
|
||||
#if OSS
|
||||
services.AddOosServices();
|
||||
#else
|
||||
services.AddCommCoreServices();
|
||||
#endif
|
||||
|
||||
// MVC
|
||||
services.AddMvc(config =>
|
||||
{
|
||||
|
26
src/Core/Context/CurrentContentProvider.cs
Normal file
26
src/Core/Context/CurrentContentProvider.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Context
|
||||
{
|
||||
public class CurrentContentProvider
|
||||
{
|
||||
public CurrentContentProvider() { }
|
||||
|
||||
public CurrentContentProvider(ProviderUser providerUser)
|
||||
{
|
||||
Id = providerUser.ProviderId;
|
||||
Type = providerUser.Type;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(providerUser.Permissions);
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Bit.Core.Repositories;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Settings;
|
||||
@ -25,6 +26,7 @@ namespace Bit.Core.Context
|
||||
public virtual DeviceType? DeviceType { get; set; }
|
||||
public virtual string IpAddress { get; set; }
|
||||
public virtual List<CurrentContentOrganization> Organizations { get; set; }
|
||||
public virtual List<CurrentContentProvider> Providers { get; set; }
|
||||
public virtual Guid? InstallationId { get; set; }
|
||||
public virtual Guid? OrganizationId { get; set; }
|
||||
public virtual bool CloudflareWorkerProxied { get; set; }
|
||||
@ -127,10 +129,19 @@ namespace Bit.Core.Context
|
||||
|
||||
DeviceIdentifier = GetClaimValue(claimsDict, "device");
|
||||
|
||||
Organizations = new List<CurrentContentOrganization>();
|
||||
Organizations = GetOrganizations(claimsDict, orgApi);
|
||||
|
||||
Providers = GetProviders(claimsDict);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private List<CurrentContentOrganization> GetOrganizations(Dictionary<string, IEnumerable<Claim>> claimsDict, bool orgApi)
|
||||
{
|
||||
var organizations = new List<CurrentContentOrganization>();
|
||||
if (claimsDict.ContainsKey("orgowner"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orgowner"].Select(c =>
|
||||
organizations.AddRange(claimsDict["orgowner"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
@ -139,7 +150,7 @@ namespace Bit.Core.Context
|
||||
}
|
||||
else if (orgApi && OrganizationId.HasValue)
|
||||
{
|
||||
Organizations.Add(new CurrentContentOrganization
|
||||
organizations.Add(new CurrentContentOrganization
|
||||
{
|
||||
Id = OrganizationId.Value,
|
||||
Type = OrganizationUserType.Owner
|
||||
@ -148,7 +159,7 @@ namespace Bit.Core.Context
|
||||
|
||||
if (claimsDict.ContainsKey("orgadmin"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orgadmin"].Select(c =>
|
||||
organizations.AddRange(claimsDict["orgadmin"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
@ -158,7 +169,7 @@ namespace Bit.Core.Context
|
||||
|
||||
if (claimsDict.ContainsKey("orguser"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orguser"].Select(c =>
|
||||
organizations.AddRange(claimsDict["orguser"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
@ -168,7 +179,7 @@ namespace Bit.Core.Context
|
||||
|
||||
if (claimsDict.ContainsKey("orgmanager"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orgmanager"].Select(c =>
|
||||
organizations.AddRange(claimsDict["orgmanager"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
@ -178,7 +189,7 @@ namespace Bit.Core.Context
|
||||
|
||||
if (claimsDict.ContainsKey("orgcustom"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orgcustom"].Select(c =>
|
||||
organizations.AddRange(claimsDict["orgcustom"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
@ -186,8 +197,34 @@ namespace Bit.Core.Context
|
||||
Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict)
|
||||
}));
|
||||
}
|
||||
|
||||
return organizations;
|
||||
}
|
||||
|
||||
private List<CurrentContentProvider> GetProviders(Dictionary<string, IEnumerable<Claim>> claimsDict)
|
||||
{
|
||||
var providers = new List<CurrentContentProvider>();
|
||||
if (claimsDict.ContainsKey("providerprovideradmin"))
|
||||
{
|
||||
providers.AddRange(claimsDict["providerprovideradmin"].Select(c =>
|
||||
new CurrentContentProvider
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
Type = ProviderUserType.ProviderAdmin
|
||||
}));
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
if (claimsDict.ContainsKey("providerserviceuser"))
|
||||
{
|
||||
providers.AddRange(claimsDict["providerserviceuser"].Select(c =>
|
||||
new CurrentContentProvider
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
Type = ProviderUserType.ServiceUser
|
||||
}));
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
public bool OrganizationUser(Guid orgId)
|
||||
@ -284,6 +321,31 @@ namespace Bit.Core.Context
|
||||
&& (o.Permissions?.ManageResetPassword ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public bool ProviderProviderAdmin(Guid providerId)
|
||||
{
|
||||
return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false;
|
||||
}
|
||||
|
||||
public bool ManageProviderUsers(Guid providerId)
|
||||
{
|
||||
return ProviderProviderAdmin(providerId);
|
||||
}
|
||||
|
||||
public bool AccessProviderOrganizations(Guid providerId)
|
||||
{
|
||||
return ProviderUser(providerId);
|
||||
}
|
||||
|
||||
public bool ManageProviderOrganizations(Guid providerId)
|
||||
{
|
||||
return ProviderProviderAdmin(providerId);
|
||||
}
|
||||
|
||||
public bool ProviderUser(Guid providerId)
|
||||
{
|
||||
return Providers?.Any(o => o.Id == providerId) ?? false;
|
||||
}
|
||||
|
||||
public async Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync(
|
||||
IOrganizationUserRepository organizationUserRepository, Guid userId)
|
||||
{
|
||||
@ -295,6 +357,18 @@ namespace Bit.Core.Context
|
||||
}
|
||||
return Organizations;
|
||||
}
|
||||
|
||||
public async Task<ICollection<CurrentContentProvider>> ProviderMembershipAsync(
|
||||
IProviderUserRepository providerUserRepository, Guid userId)
|
||||
{
|
||||
if (Providers == null)
|
||||
{
|
||||
var userProviders = await providerUserRepository.GetManyByUserAsync(userId);
|
||||
Providers = userProviders.Where(ou => ou.Status == ProviderUserStatusType.Confirmed)
|
||||
.Select(ou => new CurrentContentProvider(ou)).ToList();
|
||||
}
|
||||
return Providers;
|
||||
}
|
||||
|
||||
private string GetClaimValue(Dictionary<string, IEnumerable<Claim>> claims, string type)
|
||||
{
|
||||
|
@ -47,8 +47,16 @@ namespace Bit.Core.Context
|
||||
bool ManageSso(Guid orgId);
|
||||
bool ManageUsers(Guid orgId);
|
||||
bool ManageResetPassword(Guid orgId);
|
||||
bool ProviderProviderAdmin(Guid providerId);
|
||||
bool ProviderUser(Guid providerId);
|
||||
bool ManageProviderUsers(Guid providerId);
|
||||
bool AccessProviderOrganizations(Guid providerId);
|
||||
bool ManageProviderOrganizations(Guid providerId);
|
||||
|
||||
Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync(
|
||||
IOrganizationUserRepository organizationUserRepository, Guid userId);
|
||||
|
||||
Task<ICollection<CurrentContentProvider>> ProviderMembershipAsync(
|
||||
IProviderUserRepository providerUserRepository, Guid userId);
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,14 @@ namespace Bit.Core.IdentityServer
|
||||
"orgmanager",
|
||||
"orguser",
|
||||
"orgcustom",
|
||||
"providerprovideradmin",
|
||||
"providerserviceuser",
|
||||
}),
|
||||
new ApiResource("internal", new string[] { JwtClaimTypes.Subject }),
|
||||
new ApiResource("api.push", new string[] { JwtClaimTypes.Subject }),
|
||||
new ApiResource("api.licensing", new string[] { JwtClaimTypes.Subject }),
|
||||
new ApiResource("api.organization", new string[] { JwtClaimTypes.Subject })
|
||||
new ApiResource("api.organization", new string[] { JwtClaimTypes.Subject }),
|
||||
new ApiResource("api.provider", new string[] { JwtClaimTypes.Subject }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace Bit.Core.IdentityServer
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
|
||||
public ClientStore(
|
||||
IInstallationRepository installationRepository,
|
||||
@ -34,7 +35,8 @@ namespace Bit.Core.IdentityServer
|
||||
StaticClientStore staticClientStore,
|
||||
ILicensingService licensingService,
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationUserRepository organizationUserRepository)
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository)
|
||||
{
|
||||
_installationRepository = installationRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -44,6 +46,7 @@ namespace Bit.Core.IdentityServer
|
||||
_licensingService = licensingService;
|
||||
_currentContext = currentContext;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
}
|
||||
|
||||
public async Task<Client> FindClientByIdAsync(string clientId)
|
||||
@ -138,8 +141,9 @@ namespace Bit.Core.IdentityServer
|
||||
new ClientClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external")
|
||||
};
|
||||
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
|
||||
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
|
||||
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
|
||||
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, isPremium))
|
||||
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
|
||||
{
|
||||
var upperValue = claim.Value.ToUpperInvariant();
|
||||
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
|
||||
|
@ -18,17 +18,20 @@ namespace Bit.Core.IdentityServer
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public ProfileService(
|
||||
IUserService userService,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
ILicensingService licensingService,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_userService = userService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_licensingService = licensingService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
@ -43,7 +46,8 @@ namespace Bit.Core.IdentityServer
|
||||
{
|
||||
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
|
||||
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
|
||||
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, isPremium))
|
||||
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
|
||||
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
|
||||
{
|
||||
var upperValue = claim.Value.ToUpperInvariant();
|
||||
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
|
||||
|
@ -5,47 +5,20 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class OrganizationUserInviteRequestModel : IValidatableObject
|
||||
public class OrganizationUserInviteRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddressList]
|
||||
public IEnumerable<string> Emails { get; set; }
|
||||
[Required]
|
||||
public Enums.OrganizationUserType? Type { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (!Emails.Any())
|
||||
{
|
||||
yield return new ValidationResult("An email is required.");
|
||||
}
|
||||
|
||||
if (Emails.Count() > 20)
|
||||
{
|
||||
yield return new ValidationResult("You can only invite up to 20 users at a time.");
|
||||
}
|
||||
|
||||
var attr = new EmailAddressAttribute();
|
||||
for (var i = 0; i < Emails.Count(); i++)
|
||||
{
|
||||
var email = Emails.ElementAt(i);
|
||||
if (!attr.IsValid(email) || email.Contains(" ") || email.Contains("<"))
|
||||
{
|
||||
yield return new ValidationResult($"Email #{i + 1} is not valid.",
|
||||
new string[] { nameof(Emails) });
|
||||
}
|
||||
else if (email.Length > 256)
|
||||
{
|
||||
yield return new ValidationResult($"Email #{i + 1} is longer than 256 characters.",
|
||||
new string[] { nameof(Emails) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationUserAcceptRequestModel
|
||||
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderOrganizationAddRequestModel
|
||||
{
|
||||
[Required]
|
||||
public Guid OrganizationId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderSetupRequestModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string Name { get; set; }
|
||||
[StringLength(50)]
|
||||
public string BusinessName { get; set; }
|
||||
[Required]
|
||||
[StringLength(256)]
|
||||
[EmailAddress]
|
||||
public string BillingEmail { get; set; }
|
||||
[Required]
|
||||
public string Token { get; set; }
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
|
||||
public virtual Provider ToProvider(Provider provider)
|
||||
{
|
||||
provider.Name = Name;
|
||||
provider.BusinessName = BusinessName;
|
||||
provider.BillingEmail = BillingEmail;
|
||||
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderUserInviteRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddressList]
|
||||
public IEnumerable<string> Emails { get; set; }
|
||||
[Required]
|
||||
public ProviderUserType? Type { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserAcceptRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserConfirmRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserBulkConfirmRequestModelEntry
|
||||
{
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserBulkConfirmRequestModel
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<ProviderUserBulkConfirmRequestModelEntry> Keys { get; set; }
|
||||
|
||||
public Dictionary<Guid, string> ToDictionary()
|
||||
{
|
||||
return Keys.ToDictionary(e => e.Id, e => e.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public class ProviderUserUpdateRequestModel
|
||||
{
|
||||
[Required]
|
||||
public ProviderUserType? Type { get; set; }
|
||||
|
||||
public ProviderUser ToProviderUser(ProviderUser existingUser)
|
||||
{
|
||||
existingUser.Type = Type.Value;
|
||||
return existingUser;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProviderUserBulkRequestModel
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<Guid> Ids { get; set; }
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ namespace Bit.Core.Models.Api
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organization.Permissions);
|
||||
ResetPasswordEnrolled = organization.ResetPasswordKey != null;
|
||||
UserId = organization.UserId?.ToString();
|
||||
ProviderId = organization.ProviderId?.ToString();
|
||||
ProviderName = organization.ProviderName;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -63,5 +65,7 @@ namespace Bit.Core.Models.Api
|
||||
public bool ResetPasswordEnrolled { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public bool HasPublicAndPrivateKeys { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ namespace Bit.Core.Models.Api
|
||||
public class ProfileResponseModel : ResponseModel
|
||||
{
|
||||
public ProfileResponseModel(User user,
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails, bool twoFactorEnabled)
|
||||
: base("profile")
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails,
|
||||
IEnumerable<ProviderUserProviderDetails> providerUserDetails, bool twoFactorEnabled) : base("profile")
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
@ -30,6 +30,7 @@ namespace Bit.Core.Models.Api
|
||||
PrivateKey = user.PrivateKey;
|
||||
SecurityStamp = user.SecurityStamp;
|
||||
Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o));
|
||||
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -44,5 +45,6 @@ namespace Bit.Core.Models.Api
|
||||
public string PrivateKey { get; set; }
|
||||
public string SecurityStamp { get; set; }
|
||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
||||
public IEnumerable<ProfileProviderResponseModel> Providers { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProfileProviderResponseModel : ResponseModel
|
||||
{
|
||||
public ProfileProviderResponseModel(ProviderUserProviderDetails provider)
|
||||
: base("profileProvider")
|
||||
{
|
||||
Id = provider.ProviderId.ToString();
|
||||
Name = provider.Name;
|
||||
Key = provider.Key;
|
||||
Status = provider.Status;
|
||||
Type = provider.Type;
|
||||
Enabled = provider.Enabled;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(provider.Permissions);
|
||||
UserId = provider.UserId?.ToString();
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Key { get; set; }
|
||||
public ProviderUserStatusType Status { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderOrganizationOrganizationDetailsResponseModel : ResponseModel
|
||||
{
|
||||
public ProviderOrganizationOrganizationDetailsResponseModel(ProviderOrganizationOrganizationDetails providerOrganization,
|
||||
string obj = "providerOrganization") : base(obj)
|
||||
{
|
||||
if (providerOrganization == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(providerOrganization));
|
||||
}
|
||||
|
||||
Id = providerOrganization.Id;
|
||||
ProviderId = providerOrganization.ProviderId;
|
||||
OrganizationId = providerOrganization.OrganizationId;
|
||||
OrganizationName = providerOrganization.OrganizationName;
|
||||
Key = providerOrganization.Key;
|
||||
Settings = providerOrganization.Settings;
|
||||
CreationDate = providerOrganization.CreationDate;
|
||||
RevisionDate = providerOrganization.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public Guid ProviderId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string OrganizationName { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderResponseModel : ResponseModel
|
||||
{
|
||||
public ProviderResponseModel(Provider provider, string obj = "provider") : base(obj)
|
||||
{
|
||||
if (provider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(provider));
|
||||
}
|
||||
|
||||
Id = provider.Id;
|
||||
Name = provider.Name;
|
||||
BusinessName = provider.BusinessName;
|
||||
BusinessAddress1 = provider.BusinessAddress1;
|
||||
BusinessAddress2 = provider.BusinessAddress2;
|
||||
BusinessAddress3 = provider.BusinessAddress3;
|
||||
BusinessCountry = provider.BusinessCountry;
|
||||
BusinessTaxNumber = provider.BusinessTaxNumber;
|
||||
BillingEmail = provider.BillingEmail;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string BusinessName { get; set; }
|
||||
public string BusinessAddress1 { get; set; }
|
||||
public string BusinessAddress2 { get; set; }
|
||||
public string BusinessAddress3 { get; set; }
|
||||
public string BusinessCountry { get; set; }
|
||||
public string BusinessTaxNumber { get; set; }
|
||||
public string BillingEmail { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProviderUserResponseModel : ResponseModel
|
||||
{
|
||||
public ProviderUserResponseModel(ProviderUser providerUser, string obj = "providerUser")
|
||||
: base(obj)
|
||||
{
|
||||
if (providerUser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(providerUser));
|
||||
}
|
||||
|
||||
Id = providerUser.Id.ToString();
|
||||
UserId = providerUser.UserId?.ToString();
|
||||
Type = providerUser.Type;
|
||||
Status = providerUser.Status;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(providerUser.Permissions);
|
||||
}
|
||||
|
||||
public ProviderUserResponseModel(ProviderUserUserDetails providerUser, string obj = "providerUser")
|
||||
: base(obj)
|
||||
{
|
||||
if (providerUser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(providerUser));
|
||||
}
|
||||
|
||||
Id = providerUser.Id.ToString();
|
||||
UserId = providerUser.UserId?.ToString();
|
||||
Type = providerUser.Type;
|
||||
Status = providerUser.Status;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(providerUser.Permissions);
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public ProviderUserStatusType Status { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserUserDetailsResponseModel : ProviderUserResponseModel
|
||||
{
|
||||
public ProviderUserUserDetailsResponseModel(ProviderUserUserDetails providerUser,
|
||||
string obj = "providerUserUserDetails") : base(providerUser, obj)
|
||||
{
|
||||
if (providerUser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(providerUser));
|
||||
}
|
||||
|
||||
Name = providerUser.Name;
|
||||
Email = providerUser.Email;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserPublicKeyResponseModel : ResponseModel
|
||||
{
|
||||
public ProviderUserPublicKeyResponseModel(Guid id, string key,
|
||||
string obj = "providerUserPublicKeyResponseModel") : base(obj)
|
||||
{
|
||||
Id = id;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
}
|
||||
|
||||
public class ProviderUserBulkResponseModel : ResponseModel
|
||||
{
|
||||
public ProviderUserBulkResponseModel(Guid id, string error,
|
||||
string obj = "providerBulkConfirmResponseModel") : base(obj)
|
||||
{
|
||||
Id = id;
|
||||
Error = error;
|
||||
}
|
||||
public Guid Id { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ namespace Bit.Core.Models.Api
|
||||
User user,
|
||||
bool userTwoFactorEnabled,
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
|
||||
IEnumerable<Folder> folders,
|
||||
IEnumerable<CollectionDetails> collections,
|
||||
IEnumerable<CipherDetails> ciphers,
|
||||
@ -24,7 +25,7 @@ namespace Bit.Core.Models.Api
|
||||
IEnumerable<Send> sends)
|
||||
: base("sync")
|
||||
{
|
||||
Profile = new ProfileResponseModel(user, organizationUserDetails, userTwoFactorEnabled);
|
||||
Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, userTwoFactorEnabled);
|
||||
Folders = folders.Select(f => new FolderResponseModel(f));
|
||||
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
|
||||
Collections = collections?.Select(
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Models.Business.Provider
|
||||
@ -8,8 +9,11 @@ namespace Bit.Core.Models.Business.Provider
|
||||
{
|
||||
public IEnumerable<string> Emails { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
|
||||
public ProviderUserInvite() {}
|
||||
public ProviderUserInvite(ProviderUserInviteRequestModel requestModel)
|
||||
{
|
||||
Emails = requestModel.Emails;
|
||||
Type = requestModel.Type.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,13 @@ namespace Bit.Core.Models.Data
|
||||
public EventType Type { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? OrganizationId { get; set; }
|
||||
public Guid? ProviderId { get; set; }
|
||||
public Guid? CipherId { get; set; }
|
||||
public Guid? CollectionId { get; set; }
|
||||
public Guid? GroupId { get; set; }
|
||||
public Guid? PolicyId { get; set; }
|
||||
public Guid? OrganizationUserId { get; set; }
|
||||
public Guid? ProviderUserId { get; set; }
|
||||
public Guid? ActingUserId { get; set; }
|
||||
public DeviceType? DeviceType { get; set; }
|
||||
public string IpAddress { get; set; }
|
||||
|
@ -32,5 +32,7 @@ namespace Bit.Core.Models.Data
|
||||
public string ResetPasswordKey { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
public Guid? ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
}
|
||||
}
|
||||
|
22
src/Core/Models/Data/Provider/ProviderAbility.cs
Normal file
22
src/Core/Models/Data/Provider/ProviderAbility.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class ProviderAbility
|
||||
{
|
||||
public ProviderAbility() { }
|
||||
|
||||
public ProviderAbility(Provider provider)
|
||||
{
|
||||
Id = provider.Id;
|
||||
UseEvents = provider.UseEvents;
|
||||
Enabled = provider.Enabled;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Bit.Core.Enums.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class ProviderOrganizationOrganizationDetails
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ProviderId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string OrganizationName { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
}
|
17
src/Core/Models/Data/Provider/ProviderUserProviderDetails.cs
Normal file
17
src/Core/Models/Data/Provider/ProviderUserProviderDetails.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Bit.Core.Enums.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class ProviderUserProviderDetails
|
||||
{
|
||||
public Guid ProviderId { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Key { get; set; }
|
||||
public ProviderUserStatusType Status { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
}
|
||||
}
|
10
src/Core/Models/Data/Provider/ProviderUserPublicKey.cs
Normal file
10
src/Core/Models/Data/Provider/ProviderUserPublicKey.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class ProviderUserPublicKey
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
}
|
||||
}
|
17
src/Core/Models/Data/Provider/ProviderUserUserDetails.cs
Normal file
17
src/Core/Models/Data/Provider/ProviderUserUserDetails.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Bit.Core.Enums.Provider;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class ProviderUserUserDetails
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ProviderId { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public ProviderUserStatusType Status { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
public string ProviderId { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Url => string.Format("{0}/setup-provider?providerId={1}&email={2}&token={3}",
|
||||
public string Url => string.Format("{0}/providers/setup-provider?providerId={1}&email={2}&token={3}",
|
||||
WebVaultUrl,
|
||||
ProviderId,
|
||||
Email,
|
||||
|
@ -8,7 +8,7 @@
|
||||
public string Email { get; set; }
|
||||
public string ProviderNameUrlEncoded { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Url => string.Format("{0}/accept-provider?providerId={1}&" +
|
||||
public string Url => string.Format("{0}/providers/accept-provider?providerId={1}&" +
|
||||
"providerUserId={2}&email={3}&providerName={4}&token={5}",
|
||||
WebVaultUrl,
|
||||
ProviderId,
|
||||
|
@ -16,6 +16,7 @@ namespace Bit.Core.Models.Table.Provider
|
||||
public string BusinessTaxNumber { get; set; }
|
||||
public string BillingEmail { get; set; }
|
||||
public ProviderStatusType Status { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
|
@ -14,8 +14,8 @@ namespace Bit.Core.Models.Table.Provider
|
||||
public ProviderUserStatusType Status { get; set; }
|
||||
public ProviderUserType Type { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface IProviderOrganizationRepository : IRepository<Provider, Guid>
|
||||
public interface IProviderOrganizationRepository : IRepository<ProviderOrganization, Guid>
|
||||
{
|
||||
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
@ -8,5 +9,6 @@ namespace Bit.Core.Repositories
|
||||
public interface IProviderRepository : IRepository<Provider, Guid>
|
||||
{
|
||||
Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take);
|
||||
Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
@ -10,7 +11,13 @@ namespace Bit.Core.Repositories
|
||||
{
|
||||
Task<int> GetCountByProviderAsync(Guid providerId, string email, bool onlyRegisteredUsers);
|
||||
Task<ICollection<ProviderUser>> GetManyAsync(IEnumerable<Guid> ids);
|
||||
Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId);
|
||||
Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId);
|
||||
Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type = null);
|
||||
Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId);
|
||||
Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId,
|
||||
ProviderUserStatusType? status = null);
|
||||
Task DeleteManyAsync(IEnumerable<Guid> userIds);
|
||||
Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(Guid providerId, IEnumerable<Guid> Ids);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Settings;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class ProviderOrganizationRepository : Repository<Provider, Guid>, IProviderOrganizationRepository
|
||||
public class ProviderOrganizationRepository : Repository<ProviderOrganization, Guid>, IProviderOrganizationRepository
|
||||
{
|
||||
public ProviderOrganizationRepository(GlobalSettings globalSettings)
|
||||
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
||||
@ -13,5 +20,18 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
public ProviderOrganizationRepository(string connectionString, string readOnlyConnectionString)
|
||||
: base(connectionString, readOnlyConnectionString)
|
||||
{ }
|
||||
|
||||
public async Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderOrganizationOrganizationDetails>(
|
||||
"[dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]",
|
||||
new { ProviderId = providerId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Data;
|
||||
using Dapper;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Settings;
|
||||
|
||||
@ -34,5 +35,17 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync()
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderAbility>(
|
||||
"[dbo].[Provider_ReadAbilities]",
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
@ -48,6 +49,32 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUser>(
|
||||
"[dbo].[ProviderUser_ReadByUserId]",
|
||||
new { UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUser>(
|
||||
"[dbo].[ProviderUser_ReadByProviderIdUserId]",
|
||||
new { ProviderId = providerId, UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
@ -61,6 +88,33 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUserUserDetails>(
|
||||
"[dbo].[ProviderUserUserDetails_ReadByProviderId]",
|
||||
new { ProviderId = providerId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId,
|
||||
ProviderUserStatusType? status = null)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUserProviderDetails>(
|
||||
"[dbo].[ProviderUserProviderDetails_ReadByUserIdStatus]",
|
||||
new { UserId = userId, Status = status },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> providerUserIds)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
@ -69,5 +123,19 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
new { Ids = providerUserIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(
|
||||
Guid providerId, IEnumerable<Guid> Ids)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUserPublicKey>(
|
||||
"[dbo].[User_ReadPublicKeysByProviderUserIds]",
|
||||
new { ProviderId = providerId, ProviderUserIds = Ids.ToGuidIdArrayTVP() },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace Bit.Core.Services
|
||||
public interface IApplicationCacheService
|
||||
{
|
||||
Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync();
|
||||
Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync();
|
||||
Task UpsertOrganizationAbilityAsync(Organization organization);
|
||||
Task DeleteOrganizationAbilityAsync(Guid organizationId);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace Bit.Core.Services
|
||||
public interface IProviderService
|
||||
{
|
||||
Task CreateAsync(string ownerEmail);
|
||||
Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key);
|
||||
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key);
|
||||
Task UpdateAsync(Provider provider, bool updateBilling = false);
|
||||
|
||||
Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite);
|
||||
|
@ -223,16 +223,45 @@ namespace Bit.Core.Services
|
||||
await _eventWriteService.CreateAsync(e);
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
public Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null) => throw new NotImplementedException();
|
||||
public async Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null)
|
||||
{
|
||||
await LogProviderUsersEventAsync(new[] { (providerUser, type, date) });
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
public Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events) => throw new NotImplementedException();
|
||||
public async Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events)
|
||||
{
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
|
||||
var eventMessages = new List<IEvent>();
|
||||
foreach (var (providerUser, type, date) in events)
|
||||
{
|
||||
if (!CanUseProviderEvents(providerAbilities, providerUser.ProviderId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
eventMessages.Add(new EventMessage
|
||||
{
|
||||
ProviderId = providerUser.ProviderId,
|
||||
UserId = providerUser.UserId,
|
||||
ProviderUserId = providerUser.Id,
|
||||
Type = type,
|
||||
ActingUserId = _currentContext?.UserId,
|
||||
Date = date.GetValueOrDefault(DateTime.UtcNow)
|
||||
});
|
||||
}
|
||||
|
||||
await _eventWriteService.CreateManyAsync(eventMessages);
|
||||
}
|
||||
|
||||
private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
|
||||
{
|
||||
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
|
||||
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents;
|
||||
}
|
||||
|
||||
private bool CanUseProviderEvents(IDictionary<Guid, ProviderAbility> providerAbilities, Guid providerId)
|
||||
{
|
||||
return providerAbilities != null && providerAbilities.ContainsKey(providerId) &&
|
||||
providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -675,6 +675,7 @@ namespace Bit.Core.Services
|
||||
ProviderId = providerUser.ProviderId.ToString(),
|
||||
ProviderUserId = providerUser.Id.ToString(),
|
||||
ProviderNameUrlEncoded = WebUtility.UrlEncode(providerName),
|
||||
Token = token,
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName,
|
||||
};
|
||||
|
@ -11,14 +11,18 @@ namespace Bit.Core.Services
|
||||
public class InMemoryApplicationCacheService : IApplicationCacheService
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private DateTime _lastOrgAbilityRefresh = DateTime.MinValue;
|
||||
private IDictionary<Guid, OrganizationAbility> _orgAbilities;
|
||||
private TimeSpan _orgAbilitiesRefreshInterval = TimeSpan.FromMinutes(10);
|
||||
|
||||
private IDictionary<Guid, ProviderAbility> _providerAbilities;
|
||||
|
||||
public InMemoryApplicationCacheService(
|
||||
IOrganizationRepository organizationRepository)
|
||||
IOrganizationRepository organizationRepository, IProviderRepository providerRepository)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_providerRepository = providerRepository;
|
||||
}
|
||||
|
||||
public virtual async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync()
|
||||
@ -27,6 +31,12 @@ namespace Bit.Core.Services
|
||||
return _orgAbilities;
|
||||
}
|
||||
|
||||
public virtual async Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync()
|
||||
{
|
||||
await InitProviderAbilitiesAsync();
|
||||
return _providerAbilities;
|
||||
}
|
||||
|
||||
public virtual async Task UpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
await InitOrganizationAbilitiesAsync();
|
||||
@ -62,5 +72,16 @@ namespace Bit.Core.Services
|
||||
_lastOrgAbilityRefresh = now;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitProviderAbilitiesAsync()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (_providerAbilities == null || (now - _lastOrgAbilityRefresh) > _orgAbilitiesRefreshInterval)
|
||||
{
|
||||
var abilities = await _providerRepository.GetManyAbilitiesAsync();
|
||||
_providerAbilities = abilities.ToDictionary(a => a.Id);
|
||||
_lastOrgAbilityRefresh = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ namespace Bit.Core.Services
|
||||
|
||||
public InMemoryServiceBusApplicationCacheService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IProviderRepository providerRepository,
|
||||
GlobalSettings globalSettings)
|
||||
: base(organizationRepository)
|
||||
: base(organizationRepository, providerRepository)
|
||||
{
|
||||
_subName = CoreHelpers.GetApplicationCacheServiceBusSubcriptionName(globalSettings);
|
||||
_topicClient = new TopicClient(globalSettings.ServiceBus.ConnectionString,
|
||||
|
@ -1,348 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business.Provider;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class ProviderService : IProviderService
|
||||
{
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IUserRepository userRepository, IUserService userService, IMailService mailService,
|
||||
IDataProtectionProvider dataProtectionProvider, IEventService eventService, GlobalSettings globalSettings)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_userRepository = userRepository;
|
||||
_userService = userService;
|
||||
_mailService = mailService;
|
||||
_eventService = eventService;
|
||||
_globalSettings = globalSettings;
|
||||
_dataProtector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
|
||||
}
|
||||
|
||||
public async Task CreateAsync(string ownerEmail)
|
||||
{
|
||||
var owner = await _userRepository.GetByEmailAsync(ownerEmail);
|
||||
if (owner == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid owner.");
|
||||
}
|
||||
|
||||
var provider = new Provider
|
||||
{
|
||||
Status = ProviderStatusType.Pending,
|
||||
Enabled = true,
|
||||
};
|
||||
await _providerRepository.CreateAsync(provider);
|
||||
|
||||
var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {owner.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
||||
await _mailService.SendProviderSetupInviteEmailAsync(provider, token, owner.Email);
|
||||
}
|
||||
|
||||
public async Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key)
|
||||
{
|
||||
var owner = await _userService.GetUserByIdAsync(ownerUserId);
|
||||
if (owner == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid owner.");
|
||||
}
|
||||
|
||||
if (!CoreHelpers.TokenIsValid("ProviderSetupInvite", _dataProtector, token, owner.Email, provider.Id, _globalSettings))
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
await _providerRepository.UpsertAsync(provider);
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
ProviderId = provider.Id,
|
||||
UserId = owner.Id,
|
||||
Key = key,
|
||||
Status = ProviderUserStatusType.Confirmed,
|
||||
Type = ProviderUserType.ProviderAdmin,
|
||||
};
|
||||
|
||||
await _providerUserRepository.CreateAsync(providerUser);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Provider provider, bool updateBilling = false)
|
||||
{
|
||||
if (provider.Id == default)
|
||||
{
|
||||
throw new ApplicationException("Cannot create provider this way.");
|
||||
}
|
||||
|
||||
await _providerRepository.ReplaceAsync(provider);
|
||||
}
|
||||
|
||||
public async Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId,
|
||||
ProviderUserInvite invite)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (provider == null || invite?.Emails == null || !invite.Emails.Any())
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var providerUsers = new List<ProviderUser>();
|
||||
foreach (var email in invite.Emails)
|
||||
{
|
||||
// Make sure user is not already invited
|
||||
var existingProviderUserCount =
|
||||
await _providerUserRepository.GetCountByProviderAsync(providerId, email, false);
|
||||
if (existingProviderUserCount > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
ProviderId = providerId,
|
||||
UserId = null,
|
||||
Email = email.ToLowerInvariant(),
|
||||
Key = null,
|
||||
Type = invite.Type,
|
||||
Status = ProviderUserStatusType.Invited,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
if (invite.Permissions != null)
|
||||
{
|
||||
providerUser.Permissions = JsonSerializer.Serialize(invite.Permissions, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
}
|
||||
|
||||
await _providerUserRepository.CreateAsync(providerUser);
|
||||
|
||||
await SendInviteAsync(providerUser, provider);
|
||||
providerUsers.Add(providerUser);
|
||||
}
|
||||
|
||||
await _eventService.LogProviderUsersEventAsync(providerUsers.Select(pu => (pu, EventType.ProviderUser_Invited, null as DateTime?)));
|
||||
|
||||
return providerUsers;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId,
|
||||
IEnumerable<Guid> providerUsersId)
|
||||
{
|
||||
var providerUsers = await _providerUserRepository.GetManyAsync(providerUsersId);
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
|
||||
var result = new List<Tuple<ProviderUser, string>>();
|
||||
foreach (var providerUser in providerUsers)
|
||||
{
|
||||
if (providerUser.Status != ProviderUserStatusType.Invited || providerUser.ProviderId != providerId)
|
||||
{
|
||||
result.Add(Tuple.Create(providerUser, "User invalid."));
|
||||
continue;
|
||||
}
|
||||
|
||||
await SendInviteAsync(providerUser, provider);
|
||||
result.Add(Tuple.Create(providerUser, ""));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token)
|
||||
{
|
||||
var providerUser = await _providerUserRepository.GetByIdAsync(providerUserId);
|
||||
if (providerUser == null)
|
||||
{
|
||||
throw new BadRequestException("User invalid.");
|
||||
}
|
||||
|
||||
if (providerUser.Status != ProviderUserStatusType.Invited)
|
||||
{
|
||||
throw new BadRequestException("Already accepted.");
|
||||
}
|
||||
|
||||
if (!CoreHelpers.TokenIsValid("ProviderUserInvite", _dataProtector, token, user.Email, providerUser.Id, _globalSettings))
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(providerUser.Email) ||
|
||||
!providerUser.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new BadRequestException("User email does not match invite.");
|
||||
}
|
||||
|
||||
providerUser.Status = ProviderUserStatusType.Accepted;
|
||||
providerUser.UserId = user.Id;
|
||||
providerUser.Email = null;
|
||||
|
||||
await _providerUserRepository.ReplaceAsync(providerUser);
|
||||
|
||||
return providerUser;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<ProviderUser, string>>> ConfirmUsersAsync(Guid providerId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId)
|
||||
{
|
||||
var providerUsers = await _providerUserRepository.GetManyAsync(keys.Keys);
|
||||
var validProviderUsers = providerUsers
|
||||
.Where(u => u.UserId != null)
|
||||
.ToList();
|
||||
|
||||
if (!validProviderUsers.Any())
|
||||
{
|
||||
return new List<Tuple<ProviderUser, string>>();
|
||||
}
|
||||
|
||||
var validOrganizationUserIds = validProviderUsers.Select(u => u.UserId.Value).ToList();
|
||||
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
var users = await _userRepository.GetManyAsync(validOrganizationUserIds);
|
||||
|
||||
var keyedFilteredUsers = validProviderUsers.ToDictionary(u => u.UserId.Value, u => u);
|
||||
|
||||
var result = new List<Tuple<ProviderUser, string>>();
|
||||
var events = new List<(ProviderUser, EventType, DateTime?)>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (!keyedFilteredUsers.ContainsKey(user.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var providerUser = keyedFilteredUsers[user.Id];
|
||||
try
|
||||
{
|
||||
if (providerUser.Status != ProviderUserStatusType.Accepted || providerUser.ProviderId != providerId)
|
||||
{
|
||||
throw new BadRequestException("Invalid user.");
|
||||
}
|
||||
|
||||
providerUser.Status = ProviderUserStatusType.Confirmed;
|
||||
providerUser.Key = keys[providerUser.Id];
|
||||
providerUser.Email = null;
|
||||
|
||||
await _providerUserRepository.ReplaceAsync(providerUser);
|
||||
events.Add((providerUser, EventType.ProviderUser_Confirmed, null));
|
||||
await _mailService.SendOrganizationConfirmedEmailAsync(provider.Name, user.Email);
|
||||
result.Add(Tuple.Create(providerUser, ""));
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
result.Add(Tuple.Create(providerUser, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
await _eventService.LogProviderUsersEventAsync(events);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SaveUserAsync(ProviderUser user, Guid savingUserId)
|
||||
{
|
||||
if (user.Id.Equals(default))
|
||||
{
|
||||
throw new BadRequestException("Invite the user first.");
|
||||
}
|
||||
|
||||
if (user.Type != ProviderUserType.ProviderAdmin &&
|
||||
!await HasConfirmedProviderAdminExceptAsync(user.ProviderId, new[] {user.Id}))
|
||||
{
|
||||
throw new BadRequestException("Provider must have at least one confirmed ProviderAdmin.");
|
||||
}
|
||||
|
||||
await _providerUserRepository.ReplaceAsync(user);
|
||||
await _eventService.LogProviderUserEventAsync(user, EventType.ProviderUser_Updated);
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId,
|
||||
IEnumerable<Guid> providerUserIds, Guid deletingUserId)
|
||||
{
|
||||
var providerUsers = await _providerUserRepository.GetManyAsync(providerUserIds);
|
||||
|
||||
if (!await HasConfirmedProviderAdminExceptAsync(providerId, providerUserIds))
|
||||
{
|
||||
throw new BadRequestException("Provider must have at least one confirmed ProviderAdmin.");
|
||||
}
|
||||
|
||||
var result = new List<Tuple<ProviderUser, string>>();
|
||||
var deletedUserIds = new List<Guid>();
|
||||
var events = new List<(ProviderUser, EventType, DateTime?)>();
|
||||
|
||||
foreach (var providerUser in providerUsers)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (providerUser.ProviderId != providerId)
|
||||
{
|
||||
throw new BadRequestException("Invalid user.");
|
||||
}
|
||||
if (providerUser.UserId == deletingUserId)
|
||||
{
|
||||
throw new BadRequestException("You cannot remove yourself.");
|
||||
}
|
||||
|
||||
events.Add((providerUser, EventType.ProviderUser_Removed, null));
|
||||
|
||||
result.Add(Tuple.Create(providerUser, ""));
|
||||
deletedUserIds.Add(providerUser.Id);
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
result.Add(Tuple.Create(providerUser, e.Message));
|
||||
}
|
||||
|
||||
await _providerUserRepository.DeleteManyAsync(deletedUserIds);
|
||||
}
|
||||
|
||||
await _eventService.LogProviderUsersEventAsync(events);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
|
||||
|
||||
// TODO: Implement this
|
||||
public Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();
|
||||
|
||||
private async Task SendInviteAsync(ProviderUser providerUser, Provider provider)
|
||||
{
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var token = _dataProtector.Protect(
|
||||
$"ProviderUserInvite {providerUser.Id} {providerUser.Email} {nowMillis}");
|
||||
await _mailService.SendProviderInviteEmailAsync(provider.Name, providerUser, token, providerUser.Email);
|
||||
}
|
||||
|
||||
private async Task<bool> HasConfirmedProviderAdminExceptAsync(Guid providerId, IEnumerable<Guid> providerUserIds)
|
||||
{
|
||||
var providerAdmins = await _providerUserRepository.GetManyByProviderAsync(providerId,
|
||||
ProviderUserType.ProviderAdmin);
|
||||
var confirmedOwners = providerAdmins.Where(o => o.Status == ProviderUserStatusType.Confirmed);
|
||||
var confirmedOwnersIds = confirmedOwners.Select(u => u.Id);
|
||||
return confirmedOwnersIds.Except(providerUserIds).Any();
|
||||
}
|
||||
}
|
||||
}
|
34
src/Core/Services/NoopImplementations/NoopProviderService.cs
Normal file
34
src/Core/Services/NoopImplementations/NoopProviderService.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Business.Provider;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Table.Provider;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class NoopProviderService : IProviderService
|
||||
{
|
||||
public Task CreateAsync(string ownerEmail) => throw new NotImplementedException();
|
||||
|
||||
public Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) => throw new NotImplementedException();
|
||||
|
||||
public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException();
|
||||
|
||||
public Task<List<ProviderUser>> InviteUserAsync(Guid providerId, Guid invitingUserId, ProviderUserInvite providerUserInvite) => throw new NotImplementedException();
|
||||
|
||||
public Task<List<Tuple<ProviderUser, string>>> ResendInvitesAsync(Guid providerId, Guid invitingUserId, IEnumerable<Guid> providerUsersId) => throw new NotImplementedException();
|
||||
|
||||
public Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token) => throw new NotImplementedException();
|
||||
|
||||
public Task<List<Tuple<ProviderUser, string>>> ConfirmUsersAsync(Guid providerId, Dictionary<Guid, string> keys, Guid confirmingUserId) => throw new NotImplementedException();
|
||||
|
||||
public Task SaveUserAsync(ProviderUser user, Guid savingUserId) => throw new NotImplementedException();
|
||||
|
||||
public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException();
|
||||
|
||||
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
|
||||
|
||||
public Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ using Microsoft.Azure.Storage.Blob;
|
||||
using Bit.Core.Models.Table;
|
||||
using IdentityModel;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Enums.Provider;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
@ -737,7 +738,8 @@ namespace Bit.Core.Utilities
|
||||
return configDict;
|
||||
}
|
||||
|
||||
public static List<KeyValuePair<string, string>> BuildIdentityClaims(User user, ICollection<CurrentContentOrganization> orgs, bool isPremium)
|
||||
public static List<KeyValuePair<string, string>> BuildIdentityClaims(User user, ICollection<CurrentContentOrganization> orgs,
|
||||
ICollection<CurrentContentProvider> providers, bool isPremium)
|
||||
{
|
||||
var claims = new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
@ -849,6 +851,29 @@ namespace Bit.Core.Utilities
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (providers.Any())
|
||||
{
|
||||
foreach (var group in providers.GroupBy(o => o.Type))
|
||||
{
|
||||
switch (group.Key)
|
||||
{
|
||||
case ProviderUserType.ProviderAdmin:
|
||||
foreach (var provider in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("providerprovideradmin", provider.Id.ToString()));
|
||||
}
|
||||
break;
|
||||
case ProviderUserType.ServiceUser:
|
||||
foreach (var provider in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("providerserviceuser", provider.Id.ToString()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
|
41
src/Core/Utilities/EmailAddressListAttribute.cs
Normal file
41
src/Core/Utilities/EmailAddressListAttribute.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public class EmailAddressListAttribute : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
var emailAttribute = new EmailAddressAttribute();
|
||||
var emails = value as IList<string>;
|
||||
|
||||
if (!emails?.Any() ?? true)
|
||||
{
|
||||
return new ValidationResult("An email is required.");
|
||||
}
|
||||
|
||||
if (emails.Count() > 20)
|
||||
{
|
||||
return new ValidationResult("You can only submit up to 20 emails at a time.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < emails.Count(); i++)
|
||||
{
|
||||
var email = emails.ElementAt(i);
|
||||
if (!emailAttribute.IsValid(email) || email.Contains(" ") || email.Contains("<"))
|
||||
{
|
||||
return new ValidationResult($"Email #{i + 1} is not valid.");
|
||||
}
|
||||
|
||||
if (email.Length > 256)
|
||||
{
|
||||
return new ValidationResult($"Email #{i + 1} is longer than 256 characters.");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -126,7 +126,6 @@ namespace Bit.Core.Utilities
|
||||
services.AddSingleton<IAppleIapService, AppleIapService>();
|
||||
services.AddSingleton<ISsoConfigService, SsoConfigService>();
|
||||
services.AddScoped<ISendService, SendService>();
|
||||
services.AddScoped<IProviderService, ProviderService>();
|
||||
}
|
||||
|
||||
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
@ -265,6 +264,11 @@ namespace Bit.Core.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddOosServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IProviderService, NoopProviderService>();
|
||||
}
|
||||
|
||||
public static void AddNoopServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IMailService, NoopMailService>();
|
||||
|
@ -74,6 +74,7 @@
|
||||
<Build Include="dbo\Stored Procedures\SsoConfig_ReadByIdentifier.sql" />
|
||||
<Build Include="dbo\Stored Procedures\SsoConfig_ReadByOrganizationId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\SsoConfig_Update.sql" />
|
||||
<Build Include="dbo\Stored Procedures\User_ReadPublicKeysByProviderUserIds.sql" />
|
||||
<Build Include="dbo\Tables\Grant.sql" />
|
||||
<Build Include="dbo\Tables\SsoConfig.sql" />
|
||||
<Build Include="dbo\Tables\User.sql" />
|
||||
@ -90,6 +91,8 @@
|
||||
<Build Include="dbo\Tables\OrganizationUser.sql" />
|
||||
<Build Include="dbo\Tables\Organization.sql" />
|
||||
<Build Include="dbo\Views\GrantView.sql" />
|
||||
<Build Include="dbo\Views\ProviderOrganizationOrganizationDetailsView.sql" />
|
||||
<Build Include="dbo\Views\ProviderUserProviderDetailsView.sql" />
|
||||
<Build Include="dbo\Views\SsoConfigView.sql" />
|
||||
<Build Include="dbo\Views\UserView.sql" />
|
||||
<Build Include="dbo\Views\U2fView.sql" />
|
||||
@ -332,6 +335,7 @@
|
||||
<Build Include="dbo\Stored Procedures\Provider_DeleteById.sql" />
|
||||
<Build Include="dbo\Stored Procedures\Provider_ReadById.sql" />
|
||||
<Build Include="dbo\Stored Procedures\Provider_Search.sql" />
|
||||
<Build Include="dbo\Stored Procedures\Provider_ReadAbilities.sql" />
|
||||
<Build Include="dbo\Tables\ProviderUser.sql" />
|
||||
<Build Include="dbo\Views\ProviderUserView.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUser_Create.sql" />
|
||||
@ -343,12 +347,17 @@
|
||||
<Build Include="dbo\Stored Procedures\ProviderUser_ReadByProviderId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUser_ReadByUserId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUser_ReadCountByProviderIdEmail.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUser_ReadByProviderIdUserId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUserProviderDetails_ReadByUserIdStatus.sql" />
|
||||
<Build Include="dbo\Views\ProviderUserUserDetailsView.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderUserUserDetails_ReadByProviderId.sql" />
|
||||
<Build Include="dbo\Tables\ProviderOrganization.sql" />
|
||||
<Build Include="dbo\Views\ProviderOrganizationView.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderOrganization_Update.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderOrganization_DeleteById.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderOrganization_ReadById.sql" />
|
||||
<Build Include="dbo\Stored Procedures\ProviderOrganizationOrganizationDetails_ReadByProviderId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderId.sql" />
|
||||
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderUserId.sql" />
|
||||
<Build Include="dbo\Tables\ProviderOrganizationProviderUser.sql" />
|
||||
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]
|
||||
@ProviderId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[ProviderOrganizationOrganizationDetailsView]
|
||||
WHERE
|
||||
[ProviderId] = @ProviderId
|
||||
END
|
@ -0,0 +1,15 @@
|
||||
CREATE PROCEDURE [dbo].[ProviderUserProviderDetails_ReadByUserIdStatus]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Status TINYINT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[ProviderUserProviderDetailsView]
|
||||
WHERE
|
||||
[UserId] = @UserId
|
||||
AND (@Status IS NULL OR [Status] = @Status)
|
||||
END
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
|
||||
@ProviderId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[ProviderUserUserDetailsView]
|
||||
WHERE
|
||||
[ProviderId] = @ProviderId
|
||||
END
|
@ -28,7 +28,7 @@ BEGIN
|
||||
BEGIN
|
||||
BEGIN TRANSACTION ProviderUser_DeleteMany_PUs
|
||||
|
||||
DELETE TOP(@BatchSize) OU
|
||||
DELETE TOP(@BatchSize) PU
|
||||
FROM
|
||||
[dbo].[ProviderUser] PU
|
||||
INNER JOIN
|
||||
|
@ -0,0 +1,15 @@
|
||||
CREATE PROCEDURE [dbo].[ProviderUser_ReadByProviderIdUserId]
|
||||
@ProviderId UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[ProviderUserView]
|
||||
WHERE
|
||||
[ProviderId] = @ProviderId
|
||||
AND [UserId] = @UserId
|
||||
END
|
@ -9,6 +9,7 @@
|
||||
@BusinessTaxNumber NVARCHAR(30),
|
||||
@BillingEmail NVARCHAR(256),
|
||||
@Status TINYINT,
|
||||
@UseEvents BIT,
|
||||
@Enabled BIT,
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7)
|
||||
@ -28,6 +29,7 @@ BEGIN
|
||||
[BusinessTaxNumber],
|
||||
[BillingEmail],
|
||||
[Status],
|
||||
[UseEvents],
|
||||
[Enabled],
|
||||
[CreationDate],
|
||||
[RevisionDate]
|
||||
@ -44,6 +46,7 @@ BEGIN
|
||||
@BusinessTaxNumber,
|
||||
@BillingEmail,
|
||||
@Status,
|
||||
@UseEvents,
|
||||
@Enabled,
|
||||
@CreationDate,
|
||||
@RevisionDate
|
||||
|
12
src/Sql/dbo/Stored Procedures/Provider_ReadAbilities.sql
Normal file
12
src/Sql/dbo/Stored Procedures/Provider_ReadAbilities.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE PROCEDURE [dbo].[Provider_ReadAbilities]
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
[Id],
|
||||
[UseEvents],
|
||||
[Enabled]
|
||||
FROM
|
||||
[dbo].[Provider]
|
||||
END
|
@ -9,6 +9,7 @@
|
||||
@BusinessTaxNumber NVARCHAR(30),
|
||||
@BillingEmail NVARCHAR(256),
|
||||
@Status TINYINT,
|
||||
@UseEvents BIT,
|
||||
@Enabled BIT,
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7)
|
||||
@ -28,6 +29,7 @@ BEGIN
|
||||
[BusinessTaxNumber] = @BusinessTaxNumber,
|
||||
[BillingEmail] = @BillingEmail,
|
||||
[Status] = @Status,
|
||||
[UseEvents] = @UseEvents,
|
||||
[Enabled] = @Enabled,
|
||||
[CreationDate] = @CreationDate,
|
||||
[RevisionDate] = @RevisionDate
|
||||
|
@ -0,0 +1,19 @@
|
||||
CREATE PROCEDURE [dbo].[User_ReadPublicKeysByProviderUserIds]
|
||||
@ProviderId UNIQUEIDENTIFIER,
|
||||
@ProviderUserIds [dbo].[GuidIdArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
PU.[Id],
|
||||
U.[PublicKey]
|
||||
FROM
|
||||
@ProviderUserIds PUIDs
|
||||
INNER JOIN
|
||||
[dbo].[ProviderUser] PU ON PUIDs.Id = PU.Id AND PU.[Status] = 1 -- Accepted
|
||||
INNER JOIN
|
||||
[dbo].[User] U ON PU.UserId = U.Id
|
||||
WHERE
|
||||
PU.ProviderId = @ProviderId
|
||||
END
|
@ -9,6 +9,7 @@
|
||||
[BusinessTaxNumber] NVARCHAR (30) NULL,
|
||||
[BillingEmail] NVARCHAR (256) NULL,
|
||||
[Status] TINYINT NOT NULL,
|
||||
[UseEvents] BIT NOT NULL,
|
||||
[Enabled] BIT NOT NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
[RevisionDate] DATETIME2 (7) NOT NULL,
|
||||
|
@ -27,10 +27,16 @@ SELECT
|
||||
OU.[Status],
|
||||
OU.[Type],
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions]
|
||||
OU.[Permissions],
|
||||
PO.[ProviderId],
|
||||
P.[Name] ProviderName
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
|
||||
|
@ -0,0 +1,15 @@
|
||||
CREATE VIEW [dbo].[ProviderOrganizationOrganizationDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
PO.[Id],
|
||||
PO.[ProviderId],
|
||||
PO.[OrganizationId],
|
||||
O.[Name] OrganizationName,
|
||||
PO.[Key],
|
||||
PO.[Settings],
|
||||
PO.[CreationDate],
|
||||
PO.[RevisionDate]
|
||||
FROM
|
||||
[dbo].[ProviderOrganization] PO
|
||||
LEFT JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
|
15
src/Sql/dbo/Views/ProviderUserProviderDetailsView.sql
Normal file
15
src/Sql/dbo/Views/ProviderUserProviderDetailsView.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE VIEW [dbo].[ProviderUserProviderDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
PU.[UserId],
|
||||
PU.[ProviderId],
|
||||
P.[Name],
|
||||
PU.[Key],
|
||||
PU.[Status],
|
||||
PU.[Type],
|
||||
P.[Enabled],
|
||||
PU.[Permissions]
|
||||
FROM
|
||||
[dbo].[ProviderUser] PU
|
||||
LEFT JOIN
|
||||
[dbo].[Provider] P ON P.[Id] = PU.[ProviderId]
|
15
src/Sql/dbo/Views/ProviderUserUserDetailsView.sql
Normal file
15
src/Sql/dbo/Views/ProviderUserUserDetailsView.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE VIEW [dbo].[ProviderUserUserDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
PU.[Id],
|
||||
PU.[UserId],
|
||||
PU.[ProviderId],
|
||||
U.[Name],
|
||||
ISNULL(U.[Email], PU.[Email]) Email,
|
||||
PU.[Status],
|
||||
PU.[Type],
|
||||
PU.[Permissions]
|
||||
FROM
|
||||
[dbo].[ProviderUser] PU
|
||||
LEFT JOIN
|
||||
[dbo].[User] U ON U.[Id] = PU.[UserId]
|
Reference in New Issue
Block a user