Skip to content

Commit 9312ea6

Browse files
authored
Feature/self f4e/api keys (#1896)
* Add in ApiKey * Work on API Key table * Work on apikey table * Fix response model * Work on information for UI * Work on last sync date * Work on sync status * Work on auth * Work on tokenable * Work on merge * Add custom requirement * Add policy * Run formatting * Work on EF Migrations * Work on OrganizationConnection * Work on database * Work on additional database table * Run formatting * Small fixes * More cleanup * Cleanup * Add RevisionDate * Add GO * Finish Sql project * Add newlines * Fix stored proc file * Fix sqlproj * Add newlines * Fix table * Add navigation property * Delete Connections when organization is deleted * Add connection validation * Start adding ID column * Work on ID column * Work on SQL migration * Work on migrations * Run formatting * Fix test build * Fix sprocs * Work on migrations * Fix Create table * Fix sproc * Add prints to migration * Add default value * Update EF migrations * Formatting * Add to integration tests * Minor fixes * Formatting * Cleanup * Address PR feedback * Address more PR feedback * Fix formatting * Fix formatting * Fix * Address PR feedback * Remove accidential change * Fix SQL build * Run formatting * Address PR feedback * Add sync data to OrganizationUserOrgDetails * Add comments * Remove OrganizationConnectionService interface * Remove unused using * Address PR feedback * Formatting
1 parent 05ea5d5 commit 9312ea6

File tree

102 files changed

+6524
-644
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+6524
-644
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
]
2828
}
2929
}
30-
}
30+
}

bitwarden-server.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.Test", "test\Billin
8686
EndProject
8787
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Test", "test\Identity.Test\Identity.Test.csproj", "{310A1D8E-2D3F-4FA0-84D4-FFE31FCE193E}"
8888
EndProject
89+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.IntegrationTest", "test\Core.IntegrationTest\Core.IntegrationTest.csproj", "{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7}"
90+
EndProject
8991
Global
9092
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9193
Debug|Any CPU = Debug|Any CPU
@@ -202,6 +204,10 @@ Global
202204
{310A1D8E-2D3F-4FA0-84D4-FFE31FCE193E}.Debug|Any CPU.Build.0 = Debug|Any CPU
203205
{310A1D8E-2D3F-4FA0-84D4-FFE31FCE193E}.Release|Any CPU.ActiveCfg = Release|Any CPU
204206
{310A1D8E-2D3F-4FA0-84D4-FFE31FCE193E}.Release|Any CPU.Build.0 = Release|Any CPU
207+
{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
208+
{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
209+
{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
210+
{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7}.Release|Any CPU.Build.0 = Release|Any CPU
205211
EndGlobalSection
206212
GlobalSection(SolutionProperties) = preSolution
207213
HideSolutionNode = FALSE
@@ -234,6 +240,7 @@ Global
234240
{ED880735-0250-43C7-9662-FDC7C7416E7F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
235241
{B8639B10-2157-44BC-8CE1-D9EB4B50971F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
236242
{310A1D8E-2D3F-4FA0-84D4-FFE31FCE193E} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
243+
{EDEE89ED-89FA-4D3C-803C-AB59CE9CEBD7} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
237244
EndGlobalSection
238245
GlobalSection(ExtensibilityGlobals) = postSolution
239246
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

src/Api/Controllers/OrganizationSponsorshipsController.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Threading.Tasks;
34
using Bit.Api.Models.Request.Organizations;
5+
using Bit.Api.Models.Response;
6+
using Bit.Api.Utilities;
47
using Bit.Core.Context;
58
using Bit.Core.Entities;
9+
using Bit.Core.Enums;
610
using Bit.Core.Exceptions;
11+
using Bit.Core.Models.Business.Tokenables;
712
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
813
using Bit.Core.Repositories;
914
using Bit.Core.Services;
15+
using Bit.Core.Tokens;
1016
using Bit.Core.Utilities;
1117
using Microsoft.AspNetCore.Authorization;
1218
using Microsoft.AspNetCore.Mvc;
@@ -150,6 +156,20 @@ await _organizationRepository
150156
existingOrgSponsorship);
151157
}
152158

159+
[HttpGet("{sponsoringOrgId}/sync-status")]
160+
public async Task<object> GetSyncStatus(Guid sponsoringOrgId)
161+
{
162+
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
163+
164+
if (!await _currentContext.OrganizationOwner(sponsoringOrg.Id))
165+
{
166+
throw new NotFoundException();
167+
}
168+
169+
var lastSyncDate = await _organizationSponsorshipRepository.GetLatestSyncDateBySponsoringOrganizationIdAsync(sponsoringOrg.Id);
170+
return new OrganizationSponsorshipSyncStatusResponseModel(lastSyncDate);
171+
}
172+
153173
private Task<User> CurrentUser => _userService.GetUserByIdAsync(_currentContext.UserId.Value);
154174
}
155175
}

src/Api/Controllers/OrganizationsController.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Bit.Core.Exceptions;
1313
using Bit.Core.Models.Business;
1414
using Bit.Core.Models.Data;
15+
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
1516
using Bit.Core.Repositories;
1617
using Bit.Core.Services;
1718
using Bit.Core.Settings;
@@ -34,6 +35,9 @@ public class OrganizationsController : Controller
3435
private readonly ICurrentContext _currentContext;
3536
private readonly ISsoConfigRepository _ssoConfigRepository;
3637
private readonly ISsoConfigService _ssoConfigService;
38+
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
39+
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
40+
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
3741
private readonly GlobalSettings _globalSettings;
3842

3943
public OrganizationsController(
@@ -46,6 +50,9 @@ public OrganizationsController(
4650
ICurrentContext currentContext,
4751
ISsoConfigRepository ssoConfigRepository,
4852
ISsoConfigService ssoConfigService,
53+
IGetOrganizationApiKeyCommand getOrganizationApiKeyCommand,
54+
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
55+
IOrganizationApiKeyRepository organizationApiKeyRepository,
4956
GlobalSettings globalSettings)
5057
{
5158
_organizationRepository = organizationRepository;
@@ -57,6 +64,9 @@ public OrganizationsController(
5764
_currentContext = currentContext;
5865
_ssoConfigRepository = ssoConfigRepository;
5966
_ssoConfigService = ssoConfigService;
67+
_getOrganizationApiKeyCommand = getOrganizationApiKeyCommand;
68+
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
69+
_organizationApiKeyRepository = organizationApiKeyRepository;
6070
_globalSettings = globalSettings;
6171
}
6272

@@ -477,7 +487,7 @@ await _organizationService.ImportAsync(
477487
}
478488

479489
[HttpPost("{id}/api-key")]
480-
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] SecretVerificationRequestModel model)
490+
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
481491
{
482492
var orgIdGuid = new Guid(id);
483493
if (!await _currentContext.OrganizationOwner(orgIdGuid))
@@ -491,6 +501,9 @@ public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] SecretVerifi
491501
throw new NotFoundException();
492502
}
493503

504+
var organizationApiKey = await _getOrganizationApiKeyCommand
505+
.GetOrganizationApiKeyAsync(organization.Id, model.Type);
506+
494507
var user = await _userService.GetUserByPrincipalAsync(User);
495508
if (user == null)
496509
{
@@ -504,13 +517,27 @@ public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] SecretVerifi
504517
}
505518
else
506519
{
507-
var response = new ApiKeyResponseModel(organization);
520+
var response = new ApiKeyResponseModel(organizationApiKey);
508521
return response;
509522
}
510523
}
511524

525+
[HttpGet("{id}/api-key-information")]
526+
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id)
527+
{
528+
if (!await _currentContext.OrganizationOwner(id))
529+
{
530+
throw new NotFoundException();
531+
}
532+
533+
var apiKeys = await _organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(id);
534+
535+
return new ListResponseModel<OrganizationApiKeyInformation>(
536+
apiKeys.Select(k => new OrganizationApiKeyInformation(k)));
537+
}
538+
512539
[HttpPost("{id}/rotate-api-key")]
513-
public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] SecretVerificationRequestModel model)
540+
public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
514541
{
515542
var orgIdGuid = new Guid(id);
516543
if (!await _currentContext.OrganizationOwner(orgIdGuid))
@@ -524,6 +551,9 @@ public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] Secret
524551
throw new NotFoundException();
525552
}
526553

554+
var organizationApiKey = await _getOrganizationApiKeyCommand
555+
.GetOrganizationApiKeyAsync(organization.Id, model.Type);
556+
527557
var user = await _userService.GetUserByPrincipalAsync(User);
528558
if (user == null)
529559
{
@@ -537,8 +567,8 @@ public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] Secret
537567
}
538568
else
539569
{
540-
await _organizationService.RotateApiKeyAsync(organization);
541-
var response = new ApiKeyResponseModel(organization);
570+
await _rotateOrganizationApiKeyCommand.RotateApiKeyAsync(organizationApiKey);
571+
var response = new ApiKeyResponseModel(organizationApiKey);
542572
return response;
543573
}
544574
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Bit.Core.Enums;
2+
3+
namespace Bit.Api.Models.Request.Accounts
4+
{
5+
public class OrganizationApiKeyRequestModel : SecretVerificationRequestModel
6+
{
7+
public OrganizationApiKeyType Type { get; set; }
8+
}
9+
}

src/Api/Models/Response/ApiKeyResponseModel.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ namespace Bit.Api.Models.Response
66
{
77
public class ApiKeyResponseModel : ResponseModel
88
{
9-
public ApiKeyResponseModel(Organization organization, string obj = "apiKey")
9+
public ApiKeyResponseModel(OrganizationApiKey organizationApiKey, string obj = "apiKey")
1010
: base(obj)
1111
{
12-
if (organization == null)
12+
if (organizationApiKey == null)
1313
{
14-
throw new ArgumentNullException(nameof(organization));
14+
throw new ArgumentNullException(nameof(organizationApiKey));
1515
}
16-
ApiKey = organization.ApiKey;
16+
ApiKey = organizationApiKey.ApiKey;
17+
RevisionDate = organizationApiKey.RevisionDate;
1718
}
1819

1920
public ApiKeyResponseModel(User user, string obj = "apiKey")
@@ -24,8 +25,10 @@ public ApiKeyResponseModel(User user, string obj = "apiKey")
2425
throw new ArgumentNullException(nameof(user));
2526
}
2627
ApiKey = user.ApiKey;
28+
RevisionDate = user.RevisionDate;
2729
}
2830

2931
public string ApiKey { get; set; }
32+
public DateTime RevisionDate { get; set; }
3033
}
3134
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using Bit.Core.Entities;
3+
using Bit.Core.Enums;
4+
using Bit.Core.Models.Api;
5+
6+
namespace Bit.Api.Models.Response
7+
{
8+
public class OrganizationApiKeyInformation : ResponseModel
9+
{
10+
public OrganizationApiKeyInformation(OrganizationApiKey key) : base("keyInformation")
11+
{
12+
KeyType = key.Type;
13+
RevisionDate = key.RevisionDate;
14+
}
15+
16+
public OrganizationApiKeyType KeyType { get; set; }
17+
public DateTime RevisionDate { get; set; }
18+
}
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Bit.Core.Models.Api;
3+
4+
namespace Bit.Api.Models.Response
5+
{
6+
public class OrganizationSponsorshipSyncStatusResponseModel : ResponseModel
7+
{
8+
public OrganizationSponsorshipSyncStatusResponseModel(DateTime? lastSyncDate)
9+
: base("syncStatus")
10+
{
11+
LastSyncDate = lastSyncDate;
12+
}
13+
14+
public DateTime? LastSyncDate { get; set; }
15+
}
16+
}

src/Api/Models/Response/ProfileOrganizationResponseModel.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Bit.Core.Enums;
1+
using System;
2+
using Bit.Core.Enums;
23
using Bit.Core.Models.Api;
34
using Bit.Core.Models.Data;
45
using Bit.Core.Utilities;
@@ -45,6 +46,10 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga
4546
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
4647
.UsersCanSponsor(organization);
4748
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
49+
SponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
50+
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
51+
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;
52+
FamilySponsorshipHasSponsoredOrg = organization.FamilySponsorshipHasSponsoredOrg;
4853

4954
if (organization.SsoConfig != null)
5055
{
@@ -88,5 +93,10 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga
8893
public ProductType PlanProductType { get; set; }
8994
public bool KeyConnectorEnabled { get; set; }
9095
public string KeyConnectorUrl { get; set; }
96+
public DateTime? SponsorshipLastSyncDate { get; set; }
97+
public DateTime? FamilySponsorshipValidUntil { get; set; }
98+
public bool? FamilySponsorshipToDelete { get; set; }
99+
public bool FamilySponsorshipHasSponsoredOrg { get; set; }
100+
91101
}
92102
}

src/Api/Startup.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ public void ConfigureServices(IServiceCollection services)
114114
policy.RequireAuthenticatedUser();
115115
policy.RequireClaim(JwtClaimTypes.Scope, "api.organization");
116116
});
117+
config.AddPolicy("Installation", policy =>
118+
{
119+
policy.RequireAuthenticatedUser();
120+
policy.RequireClaim(JwtClaimTypes.Scope, "api.installation");
121+
});
117122
});
118123

119124
services.AddScoped<AuthenticatorTokenProvider>();

0 commit comments

Comments
 (0)