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

[Provider] Setup provider (#1378)

This commit is contained in:
Oscar Hinton 2021-06-30 09:35:26 +02:00 committed by GitHub
parent 08f508f536
commit 43f7271147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 1810 additions and 113 deletions

View File

@ -13,6 +13,8 @@ Each service is built and run separately. The Bitwarden clients can use differen
This means that you don't need to run all services locally for a development environment. You can run only those services that you intend to modify, and use Bitwarden.com or a self-hosted instance for all other services required.
By default some of the services depends on the Bitwarden licensed `CommCore`, however it can easily be disabled by including the `/p:DefineConstants="OSS"` as an argument to `dotnet`.
# Local Development Environment Setup
This guide will show you how to set up the Api, Identity and SQL projects for development. These are the minimum projects for any development work. You may need to set up additional projects depending on the changes you want to make.

View File

@ -61,7 +61,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Portal", "bitwarden_license
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sso", "bitwarden_license\src\Sso\Sso.csproj", "{4866AF64-6640-4C65-A662-A31E02FF9064}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Icons.Test", "test\Icons.Test\Icons.Test.csproj", "{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icons.Test", "test\Icons.Test\Icons.Test.csproj", "{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommCore", "bitwarden_license\src\CommCore\CommCore.csproj", "{EDC0D688-D58C-4CE1-AA07-3606AC6874B8}"
ProjectSection(ProjectDependencies) = postProject
{3973D21B-A692-4B60-9B70-3631C057423A} = {3973D21B-A692-4B60-9B70-3631C057423A}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommCore.Test", "bitwarden_license\test\CmmCore.Test\CommCore.Test.csproj", "{0E99A21B-684B-4C59-9831-90F775CAB6F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test - Bitwarden License", "test - Bitwarden License", "{287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -143,6 +152,14 @@ Global
{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Release|Any CPU.Build.0 = Release|Any CPU
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Release|Any CPU.Build.0 = Release|Any CPU
{0E99A21B-684B-4C59-9831-90F775CAB6F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E99A21B-684B-4C59-9831-90F775CAB6F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E99A21B-684B-4C59-9831-90F775CAB6F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E99A21B-684B-4C59-9831-90F775CAB6F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -166,6 +183,8 @@ Global
{BA852F18-852F-4154-973B-77D577B8CA04} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
{4866AF64-6640-4C65-A662-A31E02FF9064} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
{C7BA2255-C1B1-4789-8BB9-C27540DA6FB8} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
{0E99A21B-684B-4C59-9831-90F775CAB6F7} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Core\Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,6 @@
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;
@ -10,11 +9,12 @@ using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
namespace Bit.Core.Services
namespace Bit.CommCore.Services
{
public class ProviderService : IProviderService
{
@ -24,15 +24,18 @@ namespace Bit.Core.Services
private readonly GlobalSettings _globalSettings;
private readonly IProviderRepository _providerRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
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)
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
IUserService userService, IMailService mailService, IDataProtectionProvider dataProtectionProvider,
IEventService eventService, GlobalSettings globalSettings)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_userRepository = userRepository;
_userService = userService;
_mailService = mailService;
@ -55,12 +58,21 @@ namespace Bit.Core.Services
Enabled = true,
};
await _providerRepository.CreateAsync(provider);
var providerUser = new ProviderUser
{
ProviderId = provider.Id,
UserId = owner.Id,
Type = ProviderUserType.ProviderAdmin,
Status = ProviderUserStatusType.Confirmed,
};
await _providerUserRepository.CreateAsync(providerUser);
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)
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key)
{
var owner = await _userService.GetUserByIdAsync(ownerUserId);
if (owner == null)
@ -68,23 +80,29 @@ namespace Bit.Core.Services
throw new BadRequestException("Invalid owner.");
}
if (provider.Status != ProviderStatusType.Pending)
{
throw new BadRequestException("Provider is already setup.");
}
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);
var providerUser = await _providerUserRepository.GetByProviderUserAsync(provider.Id, ownerUserId);
if (!(providerUser is {Type: ProviderUserType.ProviderAdmin}))
{
throw new BadRequestException("Invalid owner.");
}
provider.Status = ProviderStatusType.Created;
await _providerRepository.UpsertAsync(provider);
providerUser.Key = key;
await _providerUserRepository.ReplaceAsync(providerUser);
return provider;
}
public async Task UpdateAsync(Provider provider, bool updateBilling = false)
@ -129,14 +147,6 @@ namespace Bit.Core.Services
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);
@ -322,8 +332,17 @@ namespace Bit.Core.Services
return result;
}
// TODO: Implement this
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
public async Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key)
{
var providerOrganization = new ProviderOrganization
{
ProviderId = providerId,
OrganizationId = organizationId,
Key = key,
};
await _providerOrganizationRepository.CreateAsync(providerOrganization);
}
// TODO: Implement this
public Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();

View File

@ -0,0 +1,14 @@
using Bit.CommCore.Services;
using Bit.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.CommCore.Utilities
{
public static class ServiceCollectionExtensions
{
public static void AddCommCoreServices(this IServiceCollection services)
{
services.AddScoped<IProviderService, ProviderService>();
}
}
}

View File

@ -3,7 +3,7 @@ using AutoFixture;
using AutoFixture.Xunit2;
using Bit.Core.Enums.Provider;
namespace Bit.Core.Test.AutoFixture.ProviderUserFixtures
namespace Bit.CommCore.Test.AutoFixture.ProviderUserFixtures
{
internal class ProviderUser : ICustomization
{

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\CommCore\CommCore.csproj" />
<ProjectReference Include="..\..\..\test\Core.Test\Core.Test.csproj" />
</ItemGroup>
</Project>

View File

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.CommCore.Test.AutoFixture.ProviderUserFixtures;
using Bit.CommCore.Services;
using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions;
@ -12,14 +14,13 @@ using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture;
using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Core.Test.AutoFixture.ProviderUserFixtures;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
using NSubstitute;
using Xunit;
using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser;
namespace Bit.Core.Test.Services
namespace Bit.CommCore.Test.Services
{
public class ProviderServiceTests
{
@ -62,27 +63,33 @@ namespace Bit.Core.Test.Services
() => sutProvider.Sut.CompleteSetupAsync(provider, user.Id, default, default));
Assert.Contains("Invalid token.", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task CompleteSetupAsync_Success(User user, Provider provider,
public async Task CompleteSetupAsync_Success(User user, Provider provider, string key,
[ProviderUser(ProviderUserStatusType.Confirmed, ProviderUserType.ProviderAdmin)]ProviderUser providerUser,
SutProvider<ProviderService> sutProvider)
{
providerUser.ProviderId = provider.Id;
providerUser.UserId = user.Id;
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(user.Id).Returns(user);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByProviderUserAsync(provider.Id, user.Id).Returns(providerUser);
var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName");
var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
var token = protector.Protect($"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
await sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, default);
var token = protector.Protect($"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
await sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, key);
await sutProvider.GetDependency<IProviderRepository>().Received().UpsertAsync(provider);
await sutProvider.GetDependency<IProviderUserRepository>().Received()
.CreateAsync(Arg.Is<ProviderUser>(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id));
.ReplaceAsync(Arg.Is<ProviderUser>(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id && pu.Key == key));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]

View File

@ -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>

View File

@ -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));
}

View File

@ -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;

View File

@ -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();

View File

@ -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 =>

View File

@ -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" />

View File

@ -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,

View 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);
}
}
}

View 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)));
}
}
}

View 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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 =>
{

View 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; }
}
}

View File

@ -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)
{

View File

@ -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);
}
}

View File

@ -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 }),
};
}
}

View File

@ -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";

View File

@ -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";

View File

@ -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

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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(

View File

@ -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;
}
}
}

View File

@ -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; }

View File

@ -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; }
}
}

View 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; }
}
}

View File

@ -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; }
}
}

View 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; }
}
}

View 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; }
}
}

View 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; }
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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()
{

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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,
};

View File

@ -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;
}
}
}
}

View File

@ -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,

View 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();
}
}

View File

@ -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;
}

View 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;
}
}
}

View File

@ -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>();

View File

@ -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" />

View File

@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]
@ProviderId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderOrganizationOrganizationDetailsView]
WHERE
[ProviderId] = @ProviderId
END

View File

@ -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

View File

@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
@ProviderId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderUserUserDetailsView]
WHERE
[ProviderId] = @ProviderId
END

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,12 @@
CREATE PROCEDURE [dbo].[Provider_ReadAbilities]
AS
BEGIN
SET NOCOUNT ON
SELECT
[Id],
[UseEvents],
[Enabled]
FROM
[dbo].[Provider]
END

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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]

View File

@ -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]

View 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]

View 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]

View File

@ -30,6 +30,7 @@ namespace Bit.Api.Test.Controllers
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IProviderUserRepository _providerUserRepository;
public AccountsControllerTests()
{
@ -39,6 +40,7 @@ namespace Bit.Api.Test.Controllers
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sut = new AccountsController(
@ -47,6 +49,7 @@ namespace Bit.Api.Test.Controllers
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_ssoUserRepository,
_userRepository,

View File

@ -5,7 +5,7 @@ using AutoFixture.Xunit2;
namespace Bit.Core.Test.AutoFixture.Attributes
{
internal class CustomAutoDataAttribute : AutoDataAttribute
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute(params Type[] iCustomizationTypes) : this(iCustomizationTypes
.Select(t => (ICustomization)Activator.CreateInstance(t)).ToArray())

View File

@ -11,12 +11,14 @@ namespace Bit.Core.Test.Services
private readonly InMemoryApplicationCacheService _sut;
private readonly IOrganizationRepository _organizationRepository;
private readonly IProviderRepository _providerRepository;
public InMemoryApplicationCacheServiceTests()
{
_organizationRepository = Substitute.For<IOrganizationRepository>();
_providerRepository = Substitute.For<IProviderRepository>();
_sut = new InMemoryApplicationCacheService(_organizationRepository);
_sut = new InMemoryApplicationCacheService(_organizationRepository, _providerRepository);
}
// Remove this test when we add actual tests. It only proves that

View File

@ -12,15 +12,18 @@ namespace Bit.Core.Test.Services
private readonly InMemoryServiceBusApplicationCacheService _sut;
private readonly IOrganizationRepository _organizationRepository;
private readonly IProviderRepository _providerRepository;
private readonly GlobalSettings _globalSettings;
public InMemoryServiceBusApplicationCacheServiceTests()
{
_organizationRepository = Substitute.For<IOrganizationRepository>();
_providerRepository = Substitute.For<IProviderRepository>();
_globalSettings = new GlobalSettings();
_sut = new InMemoryServiceBusApplicationCacheService(
_organizationRepository,
_providerRepository,
_globalSettings
);
}

View File

@ -87,6 +87,7 @@ BEGIN
[BusinessTaxNumber] NVARCHAR (30) NULL,
[BillingEmail] NVARCHAR (256) NOT NULL,
[Status] TINYINT NOT NULL,
[UseEvents] BIT NOT NULL,
[Enabled] BIT NOT NULL,
[CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL,
@ -101,6 +102,29 @@ GO
ALTER TABLE [dbo].[Provider] ALTER COLUMN [BillingEmail] NVARCHAR (256) NULL;
GO
IF COL_LENGTH('[dbo].[Provider]', 'UseEvents') IS NULL
BEGIN
ALTER TABLE
[dbo].[Provider]
ADD
[UseEvents] BIT NULL
END
GO
UPDATE
[dbo].[Provider]
SET
[UseEvents] = 0
WHERE
[UseEvents] IS NULL
GO
ALTER TABLE
[dbo].[Provider]
ALTER COLUMN
[UseEvents] BIT NOT NULL
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'ProviderView')
BEGIN
DROP VIEW [dbo].[ProviderView];
@ -132,6 +156,7 @@ CREATE PROCEDURE [dbo].[Provider_Create]
@BusinessTaxNumber NVARCHAR(30),
@BillingEmail NVARCHAR(256),
@Status TINYINT,
@UseEvents BIT,
@Enabled BIT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7)
@ -151,6 +176,7 @@ BEGIN
[BusinessTaxNumber],
[BillingEmail],
[Status],
[UseEvents],
[Enabled],
[CreationDate],
[RevisionDate]
@ -167,6 +193,7 @@ BEGIN
@BusinessTaxNumber,
@BillingEmail,
@Status,
@UseEvents,
@Enabled,
@CreationDate,
@RevisionDate
@ -191,6 +218,7 @@ CREATE PROCEDURE [dbo].[Provider_Update]
@BusinessTaxNumber NVARCHAR(30),
@BillingEmail NVARCHAR(256),
@Status TINYINT,
@UseEvents BIT,
@Enabled BIT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7)
@ -210,6 +238,7 @@ BEGIN
[BusinessTaxNumber] = @BusinessTaxNumber,
[BillingEmail] = @BillingEmail,
[Status] = @Status,
[UseEvents] = @UseEvents,
[Enabled] = @Enabled,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate
@ -923,7 +952,7 @@ BEGIN
BEGIN
BEGIN TRANSACTION ProviderUser_DeleteMany_PUs
DELETE TOP(@BatchSize) OU
DELETE TOP(@BatchSize) PU
FROM
[dbo].[ProviderUser] PU
INNER JOIN
@ -984,3 +1013,256 @@ BEGIN
END
END
GO
IF OBJECT_ID('[dbo].[ProviderUser_ReadByProviderIdUserId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderUser_ReadByProviderIdUserId]
END
GO
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
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'ProviderUserUserDetailsView')
BEGIN
DROP VIEW [dbo].[ProviderUserUserDetailsView];
END
GO
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]
GO
IF OBJECT_ID('[dbo].[ProviderUserUserDetails_ReadByProviderId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
END
GO
CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
@ProviderId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderUserUserDetailsView]
WHERE
[ProviderId] = @ProviderId
END
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'ProviderUserProviderDetailsView')
BEGIN
DROP VIEW [dbo].[ProviderUserProviderDetailsView];
END
GO
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]
GO
IF OBJECT_ID('[dbo].[ProviderUserProviderDetails_ReadByUserIdStatus]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderUserProviderDetails_ReadByUserIdStatus]
END
GO
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
GO
IF OBJECT_ID('[dbo].[Provider_ReadAbilities]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Provider_ReadAbilities]
END
GO
CREATE PROCEDURE [dbo].[Provider_ReadAbilities]
AS
BEGIN
SET NOCOUNT ON
SELECT
[Id],
[UseEvents],
[Enabled]
FROM
[dbo].[Provider]
END
GO
IF OBJECT_ID('[dbo].[User_ReadPublicKeysByProviderUserIds]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[User_ReadPublicKeysByProviderUserIds]
END
GO
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
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'ProviderOrganizationOrganizationDetailsView')
BEGIN
DROP VIEW [dbo].[ProviderOrganizationOrganizationDetailsView];
END
GO
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]
GO
IF OBJECT_ID('[dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]
END
GO
CREATE PROCEDURE [dbo].[ProviderOrganizationOrganizationDetails_ReadByProviderId]
@ProviderId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderOrganizationOrganizationDetailsView]
WHERE
[ProviderId] = @ProviderId
END
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView')
BEGIN
DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView];
END
GO
CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView]
AS
SELECT
OU.[UserId],
OU.[OrganizationId],
O.[Name],
O.[Enabled],
O.[UsePolicies],
O.[UseSso],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[UseResetPassword],
O.[SelfHost],
O.[UsersGetPremium],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
OU.[Key],
OU.[ResetPasswordKey],
O.[PublicKey],
O.[PrivateKey],
OU.[Status],
OU.[Type],
SU.[ExternalId] SsoExternalId,
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]