mirror of
https://github.com/bitwarden/server.git
synced 2025-04-06 21:48:12 -05:00
Org API controller and supporting data access
This commit is contained in:
parent
1fed877f79
commit
0b87e2c57e
@ -6,8 +6,6 @@ using Bit.Core.Repositories;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Bit.Api.Models;
|
using Bit.Api.Models;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Domains;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
|
89
src/Api/Controllers/OrganizationsController.cs
Normal file
89
src/Api/Controllers/OrganizationsController.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Bit.Api.Models;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
|
||||||
|
namespace Bit.Api.Controllers
|
||||||
|
{
|
||||||
|
[Route("organizations")]
|
||||||
|
[Authorize("Application")]
|
||||||
|
public class OrganizationsController : Controller
|
||||||
|
{
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
|
public OrganizationsController(
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IUserService userService)
|
||||||
|
{
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<OrganizationResponseModel> Get(string id)
|
||||||
|
{
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var organization = await _organizationRepository.GetByIdAsync(new Guid(id), userId);
|
||||||
|
if(organization == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrganizationResponseModel(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public async Task<ListResponseModel<OrganizationResponseModel>> Get()
|
||||||
|
{
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var organizations = await _organizationRepository.GetManyByUserIdAsync(userId);
|
||||||
|
var responses = organizations.Select(o => new OrganizationResponseModel(o));
|
||||||
|
return new ListResponseModel<OrganizationResponseModel>(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("")]
|
||||||
|
public async Task<OrganizationResponseModel> Post([FromBody]OrganizationCreateRequestModel model)
|
||||||
|
{
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var organization = model.ToOrganization(_userService.GetProperUserId(User).Value);
|
||||||
|
await _organizationRepository.ReplaceAsync(organization);
|
||||||
|
return new OrganizationResponseModel(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[HttpPost("{id}")]
|
||||||
|
public async Task<OrganizationResponseModel> Put(string id, [FromBody]OrganizationUpdateRequestModel model)
|
||||||
|
{
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var organization = await _organizationRepository.GetByIdAsync(new Guid(id), userId);
|
||||||
|
// TODO: Permission checks
|
||||||
|
if(organization == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _organizationRepository.ReplaceAsync(model.ToOrganization(organization));
|
||||||
|
return new OrganizationResponseModel(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[HttpPost("{id}/delete")]
|
||||||
|
public async Task Delete(string id)
|
||||||
|
{
|
||||||
|
var organization = await _organizationRepository.GetByIdAsync(new Guid(id),
|
||||||
|
_userService.GetProperUserId(User).Value);
|
||||||
|
if(organization == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _organizationRepository.DeleteAsync(organization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using Bit.Core.Domains;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Api.Models
|
||||||
|
{
|
||||||
|
public class OrganizationCreateRequestModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public PlanType Plan { get; set; }
|
||||||
|
// TODO: Billing info for paid plans.
|
||||||
|
|
||||||
|
public virtual Organization ToOrganization(Guid userId)
|
||||||
|
{
|
||||||
|
var organization = new Organization
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Name = Name,
|
||||||
|
Plan = Plan
|
||||||
|
};
|
||||||
|
|
||||||
|
return organization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
using Bit.Core.Domains;
|
||||||
|
|
||||||
|
namespace Bit.Api.Models
|
||||||
|
{
|
||||||
|
public class OrganizationUpdateRequestModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public virtual Organization ToOrganization(Organization existingOrganization)
|
||||||
|
{
|
||||||
|
existingOrganization.Name = Name;
|
||||||
|
return existingOrganization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/Api/Models/Response/OrganizationResponseModel.cs
Normal file
28
src/Api/Models/Response/OrganizationResponseModel.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Domains;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Api.Models
|
||||||
|
{
|
||||||
|
public class OrganizationResponseModel : ResponseModel
|
||||||
|
{
|
||||||
|
public OrganizationResponseModel(Organization organization)
|
||||||
|
: base("organization")
|
||||||
|
{
|
||||||
|
if(organization == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(organization));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id = organization.Id.ToString();
|
||||||
|
Name = organization.Name;
|
||||||
|
Plan = organization.Plan;
|
||||||
|
MaxUsers = organization.MaxUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public PlanType Plan { get; set; }
|
||||||
|
public short MaxUsers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -76,6 +76,8 @@ namespace Bit.Api
|
|||||||
services.AddSingleton<ICipherRepository, SqlServerRepos.CipherRepository>();
|
services.AddSingleton<ICipherRepository, SqlServerRepos.CipherRepository>();
|
||||||
services.AddSingleton<IDeviceRepository, SqlServerRepos.DeviceRepository>();
|
services.AddSingleton<IDeviceRepository, SqlServerRepos.DeviceRepository>();
|
||||||
services.AddSingleton<IGrantRepository, SqlServerRepos.GrantRepository>();
|
services.AddSingleton<IGrantRepository, SqlServerRepos.GrantRepository>();
|
||||||
|
services.AddSingleton<IOrganizationRepository, SqlServerRepos.OrganizationRepository>();
|
||||||
|
services.AddSingleton<IOrganizationUserRepository, SqlServerRepos.OrganizationUserRepository>();
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
services.AddScoped<CurrentContext>();
|
services.AddScoped<CurrentContext>();
|
||||||
|
22
src/Core/Domains/Organization.cs
Normal file
22
src/Core/Domains/Organization.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Domains
|
||||||
|
{
|
||||||
|
public class Organization : IDataObject<Guid>
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public PlanType Plan { get; set; }
|
||||||
|
public short MaxUsers { get; set; }
|
||||||
|
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public void SetNewId()
|
||||||
|
{
|
||||||
|
Id = CoreHelpers.GenerateComb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/Core/Domains/OrganizationUser.cs
Normal file
24
src/Core/Domains/OrganizationUser.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Domains
|
||||||
|
{
|
||||||
|
public class OrganizationUser : IDataObject<Guid>
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid OrganizationId { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public OrganizationUserStatusType Status { get; set; }
|
||||||
|
public OrganizationUserType Type { get; set; }
|
||||||
|
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public void SetNewId()
|
||||||
|
{
|
||||||
|
Id = CoreHelpers.GenerateComb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Core/Enums/OrganizationUserStatusType.cs
Normal file
10
src/Core/Enums/OrganizationUserStatusType.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum OrganizationUserStatusType : byte
|
||||||
|
{
|
||||||
|
Invited = 0,
|
||||||
|
Pending = 1,
|
||||||
|
Accepted = 2,
|
||||||
|
Confirmed = 3
|
||||||
|
}
|
||||||
|
}
|
9
src/Core/Enums/OrganizationUserType.cs
Normal file
9
src/Core/Enums/OrganizationUserType.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum OrganizationUserType : byte
|
||||||
|
{
|
||||||
|
Owner = 0,
|
||||||
|
Admin = 1,
|
||||||
|
Regular = 2
|
||||||
|
}
|
||||||
|
}
|
10
src/Core/Enums/PlanType.cs
Normal file
10
src/Core/Enums/PlanType.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum PlanType : byte
|
||||||
|
{
|
||||||
|
Free = 0,
|
||||||
|
Family = 1,
|
||||||
|
Teams = 2,
|
||||||
|
Enterprise = 3
|
||||||
|
}
|
||||||
|
}
|
13
src/Core/Repositories/IOrganizationRepository.cs
Normal file
13
src/Core/Repositories/IOrganizationRepository.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Domains;
|
||||||
|
|
||||||
|
namespace Bit.Core.Repositories
|
||||||
|
{
|
||||||
|
public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||||
|
{
|
||||||
|
Task<Organization> GetByIdAsync(Guid id, Guid userId);
|
||||||
|
Task<ICollection<Organization>> GetManyByUserIdAsync(Guid userId);
|
||||||
|
}
|
||||||
|
}
|
12
src/Core/Repositories/IOrganizationUserRepository.cs
Normal file
12
src/Core/Repositories/IOrganizationUserRepository.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Domains;
|
||||||
|
|
||||||
|
namespace Bit.Core.Repositories
|
||||||
|
{
|
||||||
|
public interface IOrganizationUserRepository : IRepository<OrganizationUser, Guid>
|
||||||
|
{
|
||||||
|
Task<OrganizationUser> GetByIdAsync(Guid id, Guid userId);
|
||||||
|
}
|
||||||
|
}
|
48
src/Core/Repositories/SqlServer/OrganizationRepository.cs
Normal file
48
src/Core/Repositories/SqlServer/OrganizationRepository.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Domains;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Data;
|
||||||
|
using Dapper;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Bit.Core.Repositories.SqlServer
|
||||||
|
{
|
||||||
|
public class OrganizationRepository : Repository<Organization, Guid>, IOrganizationRepository
|
||||||
|
{
|
||||||
|
public OrganizationRepository(GlobalSettings globalSettings)
|
||||||
|
: this(globalSettings.SqlServer.ConnectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public OrganizationRepository(string connectionString)
|
||||||
|
: base(connectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async Task<Organization> GetByIdAsync(Guid id, Guid userId)
|
||||||
|
{
|
||||||
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<Organization>(
|
||||||
|
"[dbo].[Organization_ReadByIdUserId]",
|
||||||
|
new { Id = id, UserId = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<Organization>> GetManyByUserIdAsync(Guid userId)
|
||||||
|
{
|
||||||
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<Organization>(
|
||||||
|
"[dbo].[Organization_ReadUserId]",
|
||||||
|
new { UserId = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Domains;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Bit.Core.Repositories.SqlServer
|
||||||
|
{
|
||||||
|
public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IOrganizationUserRepository
|
||||||
|
{
|
||||||
|
public OrganizationUserRepository(GlobalSettings globalSettings)
|
||||||
|
: this(globalSettings.SqlServer.ConnectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public OrganizationUserRepository(string connectionString)
|
||||||
|
: base(connectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async Task<OrganizationUser> GetByIdAsync(Guid id, Guid userId)
|
||||||
|
{
|
||||||
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<OrganizationUser>(
|
||||||
|
"[dbo].[OrganizationUser_ReadByIdUserId]",
|
||||||
|
new { Id = id, UserId = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -137,5 +137,10 @@
|
|||||||
<Build Include="dbo\Stored Procedures\Grant_ReadBySubjectId.sql" />
|
<Build Include="dbo\Stored Procedures\Grant_ReadBySubjectId.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\Grant_Save.sql" />
|
<Build Include="dbo\Stored Procedures\Grant_Save.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\User_ReadAccountRevisionDateById.sql" />
|
<Build Include="dbo\Stored Procedures\User_ReadAccountRevisionDateById.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Organization_ReadByIdUserId.sql" />
|
||||||
|
<Build Include="dbo\Views\OrganizationView.sql" />
|
||||||
|
<Build Include="dbo\Views\OrganizationUserView.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\OrganizationUser_ReadByIdUserId.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Organization_ReadByUserId.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -0,0 +1,15 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByIdUserId]
|
||||||
|
@Id UNIQUEIDENTIFIER,
|
||||||
|
@UserId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
[dbo].[OrganizationUserView]
|
||||||
|
WHERE
|
||||||
|
[Id] = @Id
|
||||||
|
AND [UserId] = @UserId
|
||||||
|
END
|
@ -0,0 +1,17 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Organization_ReadByIdUserId]
|
||||||
|
@Id UNIQUEIDENTIFIER,
|
||||||
|
@UserId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
O.*
|
||||||
|
FROM
|
||||||
|
[dbo].[OrganizationView] O
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] OU ON O.[Id] = OU.[OrganizationId]
|
||||||
|
WHERE
|
||||||
|
O.[Id] = @Id
|
||||||
|
AND OU.[UserId] = @UserId
|
||||||
|
END
|
15
src/Sql/dbo/Stored Procedures/Organization_ReadByUserId.sql
Normal file
15
src/Sql/dbo/Stored Procedures/Organization_ReadByUserId.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Organization_ReadByUserId]
|
||||||
|
@UserId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
O.*
|
||||||
|
FROM
|
||||||
|
[dbo].[OrganizationView] O
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] OU ON O.[Id] = OU.[OrganizationId]
|
||||||
|
WHERE
|
||||||
|
OU.[UserId] = @UserId
|
||||||
|
END
|
6
src/Sql/dbo/Views/OrganizationUserView.sql
Normal file
6
src/Sql/dbo/Views/OrganizationUserView.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE VIEW [dbo].[OrganizationUserView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
[dbo].[OrganizationUser]
|
6
src/Sql/dbo/Views/OrganizationView.sql
Normal file
6
src/Sql/dbo/Views/OrganizationView.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE VIEW [dbo].[OrganizationView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
[dbo].[Organization]
|
Loading…
x
Reference in New Issue
Block a user