mirror of
https://github.com/bitwarden/server
synced 2026-01-02 16:43:25 +00:00
Merge branch 'master' into feature/billing-obfuscation
This commit is contained in:
31
src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs
Normal file
31
src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Jobs;
|
||||
using Bit.Core.Services;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DeleteUnverifiedOrganizationDomainsJob : BaseJob
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DeleteUnverifiedOrganizationDomainsJob(
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<DeleteUnverifiedOrganizationDomainsJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: Start");
|
||||
using (var serviceScope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var organizationDomainService =
|
||||
serviceScope.ServiceProvider.GetRequiredService<IOrganizationDomainService>();
|
||||
await organizationDomainService.OrganizationDomainMaintenanceAsync();
|
||||
}
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: End");
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,11 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 */15 * ? * *")
|
||||
.Build();
|
||||
var everyDayAtTwoAmUtcTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryDayAtTwoAmUtcTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 2 ? * * *")
|
||||
.Build();
|
||||
|
||||
var jobs = new List<Tuple<Type, ITrigger>>
|
||||
{
|
||||
@@ -74,6 +79,7 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteCiphersJob), everyDayAtMidnightUtc),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteAuthRequestsJob), everyFifteenMinutesTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteUnverifiedOrganizationDomainsJob), everyDayAtTwoAmUtcTrigger),
|
||||
};
|
||||
|
||||
if (!_globalSettings.SelfHosted)
|
||||
@@ -98,5 +104,6 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
services.AddTransient<DeleteSendsJob>();
|
||||
services.AddTransient<DeleteCiphersJob>();
|
||||
services.AddTransient<DeleteAuthRequestsJob>();
|
||||
services.AddTransient<DeleteUnverifiedOrganizationDomainsJob>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class OrganizationViewModel
|
||||
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
|
||||
UserAcceptedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Accepted);
|
||||
UserConfirmedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Confirmed);
|
||||
OccupiedSeatCount = orgUsers.Count(u => u.OccupiesOrganizationSeat);
|
||||
OccupiedSeatCount = UserInvitedCount + UserAcceptedCount + UserConfirmedCount;
|
||||
CipherCount = ciphers.Count();
|
||||
CollectionCount = collections.Count();
|
||||
GroupCount = groups?.Count() ?? 0;
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
},
|
||||
"Azure.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.24.0",
|
||||
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
|
||||
"resolved": "1.25.0",
|
||||
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
|
||||
"System.Diagnostics.DiagnosticSource": "4.6.0",
|
||||
@@ -132,28 +132,28 @@
|
||||
},
|
||||
"Azure.Storage.Blobs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.11.0",
|
||||
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
|
||||
"resolved": "12.14.1",
|
||||
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.10.0",
|
||||
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
|
||||
"resolved": "12.13.0",
|
||||
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
|
||||
"dependencies": {
|
||||
"Azure.Core": "1.22.0",
|
||||
"Azure.Core": "1.25.0",
|
||||
"System.IO.Hashing": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Queues": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.9.0",
|
||||
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
|
||||
"resolved": "12.12.0",
|
||||
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Memory.Data": "1.0.2",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
@@ -182,21 +182,29 @@
|
||||
},
|
||||
"dbup-core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "CR00QMAtHjfeMhwxFC5haoA0q4KZ5s6Y/AdZaT6oFjySik2eFEqVasuLgWSPKSiR7ti3z01BtiR7aD3nVckAsg==",
|
||||
"resolved": "5.0.8",
|
||||
"contentHash": "d+3RxJDftcarp1Y7jI78HRdRWRC7VFjM+rB2CFHWDmao6OixuLrqiyEo1DeuMNrWLTR5mmE8p1YTpFOvozI9ZQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.4.0",
|
||||
"Microsoft.CSharp": "4.7.0",
|
||||
"System.Diagnostics.TraceSource": "4.3.0"
|
||||
}
|
||||
},
|
||||
"dbup-sqlserver": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "/4hy4qmbWmtbLJGq8XCH3mtlgMld2G8rbXcjNDhqkq5y6dGZDW03OI4UsnQRxBiTQD5aYOcLuycK1dCJYhkdSw==",
|
||||
"resolved": "5.0.8",
|
||||
"contentHash": "b954l5Zgj9qgHtm16SLq2qGLJ0gIZtrWdh6JHoUsCLMHYW+0K2Oevabquw447At4U6X2t4CNuy7ZLHYf/Z/8yg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Azure.Services.AppAuthentication": "1.3.1",
|
||||
"System.Data.SqlClient": "4.6.0",
|
||||
"dbup-core": "4.5.0"
|
||||
"Microsoft.Azure.Services.AppAuthentication": "1.6.2",
|
||||
"Microsoft.Data.SqlClient": "5.0.1",
|
||||
"dbup-core": "5.0.8"
|
||||
}
|
||||
},
|
||||
"DnsClient": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Fido2": {
|
||||
@@ -494,10 +502,10 @@
|
||||
},
|
||||
"Microsoft.Azure.Services.AppAuthentication": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.3.1",
|
||||
"contentHash": "59CEcmUSlg5nYOzcyhdoUu+EQH4wrjCKj7dNuuPMeIjDCikAON9/KQXTQLfzfWTjDwqHIRptAAj0DTBp25lFcg==",
|
||||
"resolved": "1.6.2",
|
||||
"contentHash": "rSQhTv43ionr9rWvE4vxIe/i73XR5hoBYfh7UUgdaVOGW1MZeikR9RmgaJhonTylimCcCuJvrU0zXsSIFOsTGw==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Clients.ActiveDirectory": "4.3.0",
|
||||
"Microsoft.IdentityModel.Clients.ActiveDirectory": "5.2.9",
|
||||
"System.Diagnostics.Process": "4.3.0"
|
||||
}
|
||||
},
|
||||
@@ -1081,15 +1089,22 @@
|
||||
},
|
||||
"Microsoft.IdentityModel.Clients.ActiveDirectory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "IRXnTCHxwnpnGBHVnTWd8RBJk7nsBsNZVl8j20kh234bP+oBILkt+6Iw5vQg5Q+sZmALt3Oq6X3Kx7qY71XyVw==",
|
||||
"resolved": "5.2.9",
|
||||
"contentHash": "WhBAG/9hWiMHIXve4ZgwXP3spRwf7kFFfejf76QA5BvumgnPp8iDkDCiJugzAcpW1YaHB526z1UVxHhVT1E5qw==",
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.3.0",
|
||||
"NETStandard.Library": "1.6.1",
|
||||
"System.ComponentModel.TypeConverter": "4.3.0",
|
||||
"System.Dynamic.Runtime": "4.3.0",
|
||||
"System.Net.Http": "4.3.4",
|
||||
"System.Private.Uri": "4.3.2",
|
||||
"System.Runtime.Serialization.Formatters": "4.3.0",
|
||||
"System.Runtime.Serialization.Json": "4.3.0",
|
||||
"System.Runtime.Serialization.Primitives": "4.3.0",
|
||||
"System.Security.Cryptography.X509Certificates": "4.3.0",
|
||||
"System.Security.SecureString": "4.3.0",
|
||||
"System.Xml.XDocument": "4.3.0"
|
||||
"System.Xml.XDocument": "4.3.0",
|
||||
"System.Xml.XmlDocument": "4.3.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.JsonWebTokens": {
|
||||
@@ -1513,16 +1528,6 @@
|
||||
"Microsoft.NETCore.Targets": "1.1.0"
|
||||
}
|
||||
},
|
||||
"runtime.native.System.Data.SqlClient.sni": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "AJfX7owAAkMjWQYhoml5IBfXh8UyYPjktn8pK0BFGAdKgBS7HqMz1fw5vdzfZUWfhtTPDGCjgNttt46ZyEmSjg==",
|
||||
"dependencies": {
|
||||
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
|
||||
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
|
||||
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
|
||||
}
|
||||
},
|
||||
"runtime.native.System.IO.Compression": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
@@ -1615,21 +1620,6 @@
|
||||
"resolved": "4.3.2",
|
||||
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
|
||||
},
|
||||
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
|
||||
},
|
||||
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
|
||||
},
|
||||
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
|
||||
},
|
||||
"SendGrid": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.27.0",
|
||||
@@ -1904,29 +1894,69 @@
|
||||
},
|
||||
"System.Collections.NonGeneric": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.0.1",
|
||||
"contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
|
||||
"dependencies": {
|
||||
"System.Diagnostics.Debug": "4.0.11",
|
||||
"System.Globalization": "4.0.11",
|
||||
"System.Resources.ResourceManager": "4.0.1",
|
||||
"System.Runtime": "4.1.0",
|
||||
"System.Runtime.Extensions": "4.1.0",
|
||||
"System.Threading": "4.0.11"
|
||||
"System.Diagnostics.Debug": "4.3.0",
|
||||
"System.Globalization": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Extensions": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Collections.Specialized": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.0.1",
|
||||
"contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
|
||||
"dependencies": {
|
||||
"System.Collections.NonGeneric": "4.0.1",
|
||||
"System.Globalization": "4.0.11",
|
||||
"System.Globalization.Extensions": "4.0.1",
|
||||
"System.Resources.ResourceManager": "4.0.1",
|
||||
"System.Runtime": "4.1.0",
|
||||
"System.Runtime.Extensions": "4.1.0",
|
||||
"System.Threading": "4.0.11"
|
||||
"System.Collections.NonGeneric": "4.3.0",
|
||||
"System.Globalization": "4.3.0",
|
||||
"System.Globalization.Extensions": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Extensions": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
|
||||
"dependencies": {
|
||||
"System.ComponentModel": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.TypeConverter": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
|
||||
"dependencies": {
|
||||
"System.Collections": "4.3.0",
|
||||
"System.Collections.NonGeneric": "4.3.0",
|
||||
"System.Collections.Specialized": "4.3.0",
|
||||
"System.ComponentModel": "4.3.0",
|
||||
"System.ComponentModel.Primitives": "4.3.0",
|
||||
"System.Globalization": "4.3.0",
|
||||
"System.Linq": "4.3.0",
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Reflection.Extensions": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Reflection.TypeExtensions": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Extensions": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Composition": {
|
||||
@@ -2046,17 +2076,6 @@
|
||||
"System.Text.Encoding": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Data.SqlClient": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.0",
|
||||
"contentHash": "gwItUWW1BMCckicFO85c8frFaMK8SGqYn5IeA3GSX4Lmid+CjXETfoHz7Uv+Vx6L0No7iRc/7cBL8gd6o9k9/g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "4.5.0",
|
||||
"System.Security.Principal.Windows": "4.5.0",
|
||||
"System.Text.Encoding.CodePages": "4.5.0",
|
||||
"runtime.native.System.Data.SqlClient.sni": "4.5.0"
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.Debug": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
@@ -2160,24 +2179,23 @@
|
||||
},
|
||||
"System.Dynamic.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.0.11",
|
||||
"contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==",
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.11",
|
||||
"System.Diagnostics.Debug": "4.0.11",
|
||||
"System.Globalization": "4.0.11",
|
||||
"System.Linq": "4.1.0",
|
||||
"System.Linq.Expressions": "4.1.0",
|
||||
"System.ObjectModel": "4.0.12",
|
||||
"System.Reflection": "4.1.0",
|
||||
"System.Reflection.Emit": "4.0.1",
|
||||
"System.Reflection.Emit.ILGeneration": "4.0.1",
|
||||
"System.Reflection.Primitives": "4.0.1",
|
||||
"System.Reflection.TypeExtensions": "4.1.0",
|
||||
"System.Resources.ResourceManager": "4.0.1",
|
||||
"System.Runtime": "4.1.0",
|
||||
"System.Runtime.Extensions": "4.1.0",
|
||||
"System.Threading": "4.0.11"
|
||||
"System.Collections": "4.3.0",
|
||||
"System.Diagnostics.Debug": "4.3.0",
|
||||
"System.Linq": "4.3.0",
|
||||
"System.Linq.Expressions": "4.3.0",
|
||||
"System.ObjectModel": "4.3.0",
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Reflection.Emit": "4.3.0",
|
||||
"System.Reflection.Emit.ILGeneration": "4.3.0",
|
||||
"System.Reflection.Primitives": "4.3.0",
|
||||
"System.Reflection.TypeExtensions": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Extensions": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Formats.Asn1": {
|
||||
@@ -2808,6 +2826,18 @@
|
||||
"System.Runtime.Extensions": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.Serialization.Formatters": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
|
||||
"dependencies": {
|
||||
"System.Collections": "4.3.0",
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Serialization.Primitives": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.Serialization.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
@@ -3299,7 +3329,7 @@
|
||||
"commercial.core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )"
|
||||
"Core": "[2023.2.0, )"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
@@ -3310,10 +3340,11 @@
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
|
||||
"Azure.Storage.Blobs": "[12.11.0, )",
|
||||
"Azure.Storage.Queues": "[12.9.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
@@ -3345,7 +3376,7 @@
|
||||
"infrastructure.dapper": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Core": "[2023.2.0, )",
|
||||
"Dapper": "[2.0.123, )"
|
||||
}
|
||||
},
|
||||
@@ -3353,7 +3384,7 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[11.0.0, )",
|
||||
"Core": "[2023.1.0, )",
|
||||
"Core": "[2023.2.0, )",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )",
|
||||
@@ -3365,38 +3396,38 @@
|
||||
"migrator": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Core": "[2023.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[6.0.0, )",
|
||||
"dbup-sqlserver": "[4.5.0, )"
|
||||
"dbup-sqlserver": "[5.0.8, )"
|
||||
}
|
||||
},
|
||||
"mysqlmigrations": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.1.0, )"
|
||||
"Core": "[2023.2.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.2.0, )"
|
||||
}
|
||||
},
|
||||
"postgresmigrations": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.1.0, )"
|
||||
"Core": "[2023.2.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.2.0, )"
|
||||
}
|
||||
},
|
||||
"sharedweb": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Infrastructure.Dapper": "[2023.1.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.1.0, )"
|
||||
"Core": "[2023.2.0, )",
|
||||
"Infrastructure.Dapper": "[2023.2.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.2.0, )"
|
||||
}
|
||||
},
|
||||
"sqlitemigrations": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "[2023.1.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.1.0, )"
|
||||
"Core": "[2023.2.0, )",
|
||||
"Infrastructure.EntityFramework": "[2023.2.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
143
src/Api/Controllers/OrganizationDomainController.cs
Normal file
143
src/Api/Controllers/OrganizationDomainController.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Bit.Api.Models.Request;
|
||||
using Bit.Api.Models.Request.Organizations;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Models.Response.Organizations;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Controllers;
|
||||
|
||||
[Route("organizations")]
|
||||
[Authorize("Application")]
|
||||
public class OrganizationDomainController : Controller
|
||||
{
|
||||
private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand;
|
||||
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
|
||||
private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand;
|
||||
private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery;
|
||||
private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
|
||||
public OrganizationDomainController(
|
||||
ICreateOrganizationDomainCommand createOrganizationDomainCommand,
|
||||
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
|
||||
IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand,
|
||||
IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery,
|
||||
IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery,
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
_createOrganizationDomainCommand = createOrganizationDomainCommand;
|
||||
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
|
||||
_deleteOrganizationDomainCommand = deleteOrganizationDomainCommand;
|
||||
_getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery;
|
||||
_getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery;
|
||||
_currentContext = currentContext;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
}
|
||||
|
||||
[HttpGet("{orgId}/domain")]
|
||||
public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
await ValidateOrganizationAccessAsync(orgIdGuid);
|
||||
|
||||
var domains = await _getOrganizationDomainByOrganizationIdQuery
|
||||
.GetDomainsByOrganizationId(orgIdGuid);
|
||||
var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList();
|
||||
return new ListResponseModel<OrganizationDomainResponseModel>(response);
|
||||
}
|
||||
|
||||
[HttpGet("{orgId}/domain/{id}")]
|
||||
public async Task<OrganizationDomainResponseModel> Get(string orgId, string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var IdGuid = new Guid(id);
|
||||
await ValidateOrganizationAccessAsync(orgIdGuid);
|
||||
|
||||
var domain = await _getOrganizationDomainByIdQuery.GetOrganizationDomainById(IdGuid);
|
||||
if (domain is null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new OrganizationDomainResponseModel(domain);
|
||||
}
|
||||
|
||||
[HttpPost("{orgId}/domain")]
|
||||
public async Task<OrganizationDomainResponseModel> Post(string orgId,
|
||||
[FromBody] OrganizationDomainRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
await ValidateOrganizationAccessAsync(orgIdGuid);
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = orgIdGuid,
|
||||
Txt = model.Txt,
|
||||
DomainName = model.DomainName.ToLower()
|
||||
};
|
||||
|
||||
var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain);
|
||||
return new OrganizationDomainResponseModel(domain);
|
||||
}
|
||||
|
||||
[HttpPost("{orgId}/domain/{id}/verify")]
|
||||
public async Task<OrganizationDomainResponseModel> Verify(string orgId, string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var idGuid = new Guid(id);
|
||||
await ValidateOrganizationAccessAsync(orgIdGuid);
|
||||
|
||||
var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
|
||||
return new OrganizationDomainResponseModel(domain);
|
||||
}
|
||||
|
||||
[HttpDelete("{orgId}/domain/{id}")]
|
||||
[HttpPost("{orgId}/domain/{id}/remove")]
|
||||
public async Task RemoveDomain(string orgId, string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var idGuid = new Guid(id);
|
||||
await ValidateOrganizationAccessAsync(orgIdGuid);
|
||||
|
||||
await _deleteOrganizationDomainCommand.DeleteAsync(idGuid);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("domain/sso/details")] // must be post to accept email cleanly
|
||||
public async Task<OrganizationDomainSsoDetailsResponseModel> GetOrgDomainSsoDetails(
|
||||
[FromBody] OrganizationDomainSsoDetailsRequestModel model)
|
||||
{
|
||||
var ssoResult = await _organizationDomainRepository.GetOrganizationDomainSsoDetailsAsync(model.Email);
|
||||
if (ssoResult is null)
|
||||
{
|
||||
throw new NotFoundException("Claimed org domain not found");
|
||||
}
|
||||
|
||||
return new OrganizationDomainSsoDetailsResponseModel(ssoResult);
|
||||
}
|
||||
|
||||
private async Task ValidateOrganizationAccessAsync(Guid orgIdGuid)
|
||||
{
|
||||
if (!await _currentContext.ManageSso(orgIdGuid))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Accounts;
|
||||
using Bit.Api.Models.Request.Organizations;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Models.Response.Organizations;
|
||||
using Bit.Api.SecretsManager;
|
||||
using Bit.Api.Utilities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
@@ -38,6 +39,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
|
||||
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
|
||||
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
@@ -55,6 +57,7 @@ public class OrganizationsController : Controller
|
||||
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
|
||||
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand,
|
||||
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
@@ -71,6 +74,7 @@ public class OrganizationsController : Controller
|
||||
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
|
||||
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
|
||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
|
||||
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
@@ -135,6 +139,7 @@ public class OrganizationsController : Controller
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo);
|
||||
}
|
||||
else
|
||||
@@ -255,7 +260,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.BusinessName ||
|
||||
model.BillingEmail != organization.BillingEmail);
|
||||
model.BillingEmail != organization.BillingEmail);
|
||||
|
||||
var hasRequiredPermissions = updateBilling
|
||||
? await _currentContext.ManageBilling(orgIdGuid)
|
||||
@@ -304,11 +309,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
|
||||
return new PaymentResponseModel
|
||||
{
|
||||
Success = result.Item1,
|
||||
PaymentIntentClientSecret = result.Item2
|
||||
};
|
||||
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 };
|
||||
}
|
||||
|
||||
[HttpPost("{id}/subscription")]
|
||||
@@ -335,11 +336,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var result = await _organizationService.AdjustSeatsAsync(orgIdGuid, model.SeatAdjustment.Value);
|
||||
return new PaymentResponseModel
|
||||
{
|
||||
Success = true,
|
||||
PaymentIntentClientSecret = result
|
||||
};
|
||||
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
|
||||
}
|
||||
|
||||
[HttpPost("{id}/storage")]
|
||||
@@ -353,11 +350,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var result = await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value);
|
||||
return new PaymentResponseModel
|
||||
{
|
||||
Success = true,
|
||||
PaymentIntentClientSecret = result
|
||||
};
|
||||
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
|
||||
}
|
||||
|
||||
[HttpPost("{id}/verify-bank")]
|
||||
@@ -471,7 +464,15 @@ public class OrganizationsController : Controller
|
||||
throw new BadRequestException("Invalid license");
|
||||
}
|
||||
|
||||
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
|
||||
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid);
|
||||
if (selfHostedOrganizationDetails == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var existingOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
|
||||
|
||||
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, existingOrganization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/import")]
|
||||
@@ -548,7 +549,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}/api-key-information/{type?}")]
|
||||
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id, [FromRoute] OrganizationApiKeyType? type)
|
||||
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id,
|
||||
[FromRoute] OrganizationApiKeyType? type)
|
||||
{
|
||||
if (!await HasApiKeyAccessAsync(id, type))
|
||||
{
|
||||
@@ -577,8 +579,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var organizationApiKey = await _getOrganizationApiKeyQuery
|
||||
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
|
||||
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
|
||||
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
|
||||
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
|
||||
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
@@ -679,7 +681,8 @@ public class OrganizationsController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey, model.EncryptedPrivateKey);
|
||||
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
|
||||
model.EncryptedPrivateKey);
|
||||
return new OrganizationKeysResponseModel(org);
|
||||
}
|
||||
|
||||
@@ -725,4 +728,34 @@ public class OrganizationsController : Controller
|
||||
|
||||
return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig);
|
||||
}
|
||||
|
||||
// This is a temporary endpoint to self-enroll in secrets manager
|
||||
[SecretsManager]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
[HttpPost("{id}/enroll-secrets-manager")]
|
||||
public async Task EnrollSecretsManager(Guid id, [FromBody] OrganizationEnrollSecretsManagerRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
if (!await _currentContext.OrganizationAdmin(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
organization.UseSecretsManager = model.Enabled;
|
||||
await _organizationService.UpdateAsync(organization);
|
||||
|
||||
// Turn on Secrets Manager for the user
|
||||
if (model.Enabled)
|
||||
{
|
||||
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(id, userId);
|
||||
orgUser.AccessSecretsManager = true;
|
||||
await _organizationUserRepository.ReplaceAsync(orgUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
|
||||
|
||||
public SelfHostedOrganizationLicensesController(
|
||||
ICurrentContext currentContext,
|
||||
@@ -34,7 +35,8 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IUserService userService)
|
||||
IUserService userService,
|
||||
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery;
|
||||
@@ -42,6 +44,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_userService = userService;
|
||||
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
@@ -79,25 +82,33 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
throw new BadRequestException("Invalid license");
|
||||
}
|
||||
|
||||
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
|
||||
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid);
|
||||
if (selfHostedOrganizationDetails == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
|
||||
|
||||
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/sync")]
|
||||
public async Task SyncLicenseAsync(string id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
|
||||
if (organization == null)
|
||||
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(new Guid(id));
|
||||
if (selfHostedOrganizationDetails == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await _currentContext.OrganizationOwner(organization.Id))
|
||||
if (!await _currentContext.OrganizationOwner(selfHostedOrganizationDetails.Id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var billingSyncConnection =
|
||||
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
|
||||
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(selfHostedOrganizationDetails.Id,
|
||||
OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
|
||||
if (billingSyncConnection == null)
|
||||
{
|
||||
@@ -105,9 +116,10 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
}
|
||||
|
||||
var license =
|
||||
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection);
|
||||
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(selfHostedOrganizationDetails, billingSyncConnection);
|
||||
var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
|
||||
|
||||
await _organizationService.UpdateLicenseAsync(organization.Id, license);
|
||||
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization);
|
||||
|
||||
var config = billingSyncConnection.GetConfig<BillingSyncConfig>();
|
||||
config.LastLicenseSync = DateTime.Now;
|
||||
|
||||
@@ -11,7 +11,6 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Duo;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -153,7 +152,7 @@ public class TwoFactorController : Controller
|
||||
try
|
||||
{
|
||||
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
|
||||
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
|
||||
await duoApi.JSONApiCall("GET", "/auth/v2/check");
|
||||
}
|
||||
catch (DuoException)
|
||||
{
|
||||
@@ -210,7 +209,7 @@ public class TwoFactorController : Controller
|
||||
try
|
||||
{
|
||||
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
|
||||
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
|
||||
await duoApi.JSONApiCall("GET", "/auth/v2/check");
|
||||
}
|
||||
catch (DuoException)
|
||||
{
|
||||
@@ -295,21 +294,14 @@ public class TwoFactorController : Controller
|
||||
if (await _verifyAuthRequestCommand
|
||||
.VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode))
|
||||
{
|
||||
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
|
||||
|
||||
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (await _userService.VerifySecretAsync(user, model.Secret))
|
||||
{
|
||||
if (await _userService.VerifySecretAsync(user, model.Secret))
|
||||
{
|
||||
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
|
||||
|
||||
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
|
||||
return;
|
||||
}
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,41 +382,18 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Leaving this for backwards compatibilty on clients")]
|
||||
[HttpGet("get-device-verification-settings")]
|
||||
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
|
||||
public Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (User.Claims.HasSsoIdP())
|
||||
{
|
||||
return new DeviceVerificationResponseModel(false, false);
|
||||
}
|
||||
|
||||
var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user);
|
||||
return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled);
|
||||
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
|
||||
}
|
||||
|
||||
[Obsolete("Leaving this for backwards compatibilty on clients")]
|
||||
[HttpPut("device-verification-settings")]
|
||||
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
|
||||
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
if (!_userService.CanEditDeviceVerificationSettings(user)
|
||||
|| User.Claims.HasSsoIdP())
|
||||
{
|
||||
throw new InvalidOperationException("Can't update device verification settings");
|
||||
}
|
||||
|
||||
model.ToUser(user);
|
||||
await _userService.SaveUserAsync(user);
|
||||
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
|
||||
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
|
||||
}
|
||||
|
||||
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
|
||||
@@ -467,17 +436,4 @@ public class TwoFactorController : Controller
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsNewDeviceLoginAsync(User user, TwoFactorEmailRequestModel model)
|
||||
{
|
||||
if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null
|
||||
&&
|
||||
await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null))
|
||||
{
|
||||
model.ToUser(user);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
.WithIntervalInHours(24)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
var validateOrganizationDomainTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("ValidateOrganizationDomainTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 * * * ?")
|
||||
.Build();
|
||||
|
||||
|
||||
var jobs = new List<Tuple<Type, ITrigger>>
|
||||
{
|
||||
@@ -54,7 +60,8 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
new Tuple<Type, ITrigger>(typeof(EmergencyAccessNotificationJob), emergencyAccessNotificationTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger)
|
||||
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger),
|
||||
};
|
||||
|
||||
if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication)
|
||||
@@ -78,5 +85,6 @@ public class JobsHostedService : BaseJobsHostedService
|
||||
services.AddTransient<EmergencyAccessTimeoutJob>();
|
||||
services.AddTransient<ValidateUsersJob>();
|
||||
services.AddTransient<ValidateOrganizationsJob>();
|
||||
services.AddTransient<ValidateOrganizationDomainJob>();
|
||||
}
|
||||
}
|
||||
|
||||
30
src/Api/Jobs/ValidateOrganizationDomainJob.cs
Normal file
30
src/Api/Jobs/ValidateOrganizationDomainJob.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Jobs;
|
||||
using Bit.Core.Services;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Api.Jobs;
|
||||
|
||||
public class ValidateOrganizationDomainJob : BaseJob
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
public ValidateOrganizationDomainJob(
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<ValidateOrganizationDomainJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: Start");
|
||||
using (var serviceScope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var organizationDomainService =
|
||||
serviceScope.ServiceProvider.GetRequiredService<IOrganizationDomainService>();
|
||||
await organizationDomainService.ValidateOrganizationsDomainAsync();
|
||||
}
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: End");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Api.Models.Request;
|
||||
|
||||
public class DeviceVerificationRequestModel
|
||||
{
|
||||
[Obsolete("Leaving this for backwards compatibilty on clients")]
|
||||
[Required]
|
||||
public bool UnknownDeviceVerificationEnabled { get; set; }
|
||||
|
||||
public User ToUser(User user)
|
||||
{
|
||||
user.UnknownDeviceVerificationEnabled = UnknownDeviceVerificationEnabled;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ public class GroupRequestModel
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public bool? AccessAll { get; set; }
|
||||
[StringLength(300)]
|
||||
public string ExternalId { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
public IEnumerable<Guid> Users { get; set; }
|
||||
|
||||
@@ -27,7 +25,6 @@ public class GroupRequestModel
|
||||
{
|
||||
existingGroup.Name = Name;
|
||||
existingGroup.AccessAll = AccessAll.Value;
|
||||
existingGroup.ExternalId = ExternalId;
|
||||
return existingGroup;
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Api/Models/Request/OrganizationDomainRequestModel.cs
Normal file
12
src/Api/Models/Request/OrganizationDomainRequestModel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Models.Request;
|
||||
|
||||
public class OrganizationDomainRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Txt { get; set; }
|
||||
|
||||
[Required]
|
||||
public string DomainName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Models.Request.Organizations;
|
||||
|
||||
public class OrganizationDomainSsoDetailsRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Api.Models.Request.Organizations;
|
||||
|
||||
public class OrganizationEnrollSecretsManagerRequestModel
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
@@ -12,9 +12,6 @@ public class OrganizationUpdateRequestModel
|
||||
public string Name { get; set; }
|
||||
[StringLength(50)]
|
||||
public string BusinessName { get; set; }
|
||||
[Obsolete("2022-08-03 Moved to Org SSO request model, left for backwards compatability. Remove with EC-489.")]
|
||||
[StringLength(50)]
|
||||
public string Identifier { get; set; }
|
||||
[EmailAddress]
|
||||
[Required]
|
||||
[StringLength(256)]
|
||||
@@ -31,7 +28,6 @@ public class OrganizationUpdateRequestModel
|
||||
existingOrganization.BusinessName = BusinessName;
|
||||
existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
}
|
||||
existingOrganization.Identifier = Identifier;
|
||||
Keys?.ToOrganization(existingOrganization);
|
||||
return existingOrganization;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Bit.Api.Models.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Utilities;
|
||||
using Fido2NetLib;
|
||||
|
||||
namespace Bit.Api.Models.Request;
|
||||
@@ -104,7 +105,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
|
||||
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (!Core.Utilities.Duo.DuoApi.ValidHost(Host))
|
||||
if (!DuoApi.ValidHost(Host))
|
||||
{
|
||||
yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) });
|
||||
}
|
||||
@@ -202,8 +203,6 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
|
||||
[StringLength(256)]
|
||||
public string Email { get; set; }
|
||||
|
||||
public string DeviceIdentifier { get; set; }
|
||||
|
||||
public string AuthRequestId { get; set; }
|
||||
|
||||
public User ToUser(User extistingUser)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Bit.Api.Models.Response;
|
||||
|
||||
[Obsolete("Leaving this for backwards compatibilty on clients")]
|
||||
public class DeviceVerificationResponseModel : ResponseModel
|
||||
{
|
||||
public DeviceVerificationResponseModel(bool isDeviceVerificationSectionEnabled, bool unknownDeviceVerificationEnabled)
|
||||
|
||||
@@ -31,6 +31,9 @@ public class EventResponseModel : ResponseModel
|
||||
IpAddress = ev.IpAddress;
|
||||
InstallationId = ev.InstallationId;
|
||||
SystemUser = ev.SystemUser;
|
||||
DomainName = ev.DomainName;
|
||||
SecretId = ev.SecretId;
|
||||
ServiceAccountId = ev.ServiceAccountId;
|
||||
}
|
||||
|
||||
public EventType Type { get; set; }
|
||||
@@ -50,4 +53,7 @@ public class EventResponseModel : ResponseModel
|
||||
public DeviceType? DeviceType { get; set; }
|
||||
public string IpAddress { get; set; }
|
||||
public EventSystemUser? SystemUser { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.Models.Response.Organizations;
|
||||
|
||||
public class OrganizationDomainResponseModel : ResponseModel
|
||||
{
|
||||
public OrganizationDomainResponseModel(OrganizationDomain organizationDomain, string obj = "organizationDomain")
|
||||
: base(obj)
|
||||
{
|
||||
if (organizationDomain == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(organizationDomain));
|
||||
}
|
||||
|
||||
Id = organizationDomain.Id.ToString();
|
||||
OrganizationId = organizationDomain.OrganizationId.ToString();
|
||||
Txt = organizationDomain.Txt;
|
||||
DomainName = organizationDomain.DomainName;
|
||||
CreationDate = organizationDomain.CreationDate;
|
||||
NextRunDate = organizationDomain.NextRunDate;
|
||||
JobRunCount = organizationDomain.JobRunCount;
|
||||
VerifiedDate = organizationDomain.VerifiedDate;
|
||||
LastCheckedDate = organizationDomain.LastCheckedDate;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string Txt { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime NextRunDate { get; set; }
|
||||
public int JobRunCount { get; set; }
|
||||
public DateTime? VerifiedDate { get; set; }
|
||||
public DateTime? LastCheckedDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
|
||||
namespace Bit.Api.Models.Response.Organizations;
|
||||
|
||||
public class OrganizationDomainSsoDetailsResponseModel : ResponseModel
|
||||
{
|
||||
public OrganizationDomainSsoDetailsResponseModel(OrganizationDomainSsoDetailsData data, string obj = "organizationDomainSsoDetails")
|
||||
: base(obj)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
SsoAvailable = data.SsoAvailable;
|
||||
DomainName = data.DomainName;
|
||||
OrganizationIdentifier = data.OrganizationIdentifier;
|
||||
SsoRequired = data.SsoRequired;
|
||||
VerifiedDate = data.VerifiedDate;
|
||||
}
|
||||
|
||||
public bool SsoAvailable { get; private set; }
|
||||
public string DomainName { get; private set; }
|
||||
public string OrganizationIdentifier { get; private set; }
|
||||
public bool SsoRequired { get; private set; }
|
||||
public DateTime? VerifiedDate { get; private set; }
|
||||
}
|
||||
@@ -17,7 +17,6 @@ public class OrganizationResponseModel : ResponseModel
|
||||
}
|
||||
|
||||
Id = organization.Id.ToString();
|
||||
Identifier = organization.Identifier;
|
||||
Name = organization.Name;
|
||||
BusinessName = organization.BusinessName;
|
||||
BusinessAddress1 = organization.BusinessAddress1;
|
||||
@@ -51,7 +50,6 @@ public class OrganizationResponseModel : ResponseModel
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string BusinessName { get; set; }
|
||||
public string BusinessAddress1 { get; set; }
|
||||
|
||||
@@ -23,6 +23,7 @@ public class OrganizationUserResponseModel : ResponseModel
|
||||
Type = organizationUser.Type;
|
||||
Status = organizationUser.Status;
|
||||
AccessAll = organizationUser.AccessAll;
|
||||
ExternalId = organizationUser.ExternalId;
|
||||
AccessSecretsManager = organizationUser.AccessSecretsManager;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
|
||||
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
|
||||
@@ -41,6 +42,7 @@ public class OrganizationUserResponseModel : ResponseModel
|
||||
Type = organizationUser.Type;
|
||||
Status = organizationUser.Status;
|
||||
AccessAll = organizationUser.AccessAll;
|
||||
ExternalId = organizationUser.ExternalId;
|
||||
AccessSecretsManager = organizationUser.AccessSecretsManager;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
|
||||
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
|
||||
@@ -52,6 +54,7 @@ public class OrganizationUserResponseModel : ResponseModel
|
||||
public OrganizationUserType Type { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public bool AccessSecretsManager { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public bool ResetPasswordEnrolled { get; set; }
|
||||
@@ -86,6 +89,7 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
|
||||
|
||||
Name = organizationUser.Name;
|
||||
Email = organizationUser.Email;
|
||||
AvatarColor = organizationUser.AvatarColor;
|
||||
TwoFactorEnabled = twoFactorEnabled;
|
||||
SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId);
|
||||
Collections = organizationUser.Collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
@@ -94,8 +98,10 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
|
||||
ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector;
|
||||
}
|
||||
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string AvatarColor { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public bool SsoBound { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; }
|
||||
|
||||
@@ -1,27 +1,54 @@
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Controllers;
|
||||
|
||||
[SecretsManager]
|
||||
[Authorize("secrets")]
|
||||
[Route("access-policies")]
|
||||
public class AccessPoliciesController : Controller
|
||||
{
|
||||
private const int _maxBulkCreation = 15;
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AccessPoliciesController(
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext,
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IProjectRepository projectRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICreateAccessPoliciesCommand createAccessPoliciesCommand,
|
||||
IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
|
||||
IUpdateAccessPolicyCommand updateAccessPolicyCommand)
|
||||
{
|
||||
_userService = userService;
|
||||
_currentContext = currentContext;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
_createAccessPoliciesCommand = createAccessPoliciesCommand;
|
||||
_deleteAccessPolicyCommand = deleteAccessPolicyCommand;
|
||||
@@ -32,37 +59,244 @@ public class AccessPoliciesController : Controller
|
||||
public async Task<ProjectAccessPoliciesResponseModel> CreateProjectAccessPoliciesAsync([FromRoute] Guid id,
|
||||
[FromBody] AccessPoliciesCreateRequest request)
|
||||
{
|
||||
if (request.Count() > _maxBulkCreation)
|
||||
{
|
||||
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
|
||||
}
|
||||
|
||||
var project = await _projectRepository.GetByIdAsync(id);
|
||||
if (project == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId);
|
||||
var policies = request.ToBaseAccessPoliciesForProject(id);
|
||||
var results = await _createAccessPoliciesCommand.CreateAsync(policies);
|
||||
await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient);
|
||||
var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
|
||||
return new ProjectAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpGet("/projects/{id}/access-policies")]
|
||||
public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id)
|
||||
{
|
||||
var project = await _projectRepository.GetByIdAsync(id);
|
||||
await CheckUserHasWriteAccessToProjectAsync(project);
|
||||
|
||||
var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
|
||||
return new ProjectAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpPost("/service-accounts/{id}/access-policies")]
|
||||
public async Task<ServiceAccountAccessPoliciesResponseModel> CreateServiceAccountAccessPoliciesAsync(
|
||||
[FromRoute] Guid id,
|
||||
[FromBody] AccessPoliciesCreateRequest request)
|
||||
{
|
||||
if (request.Count() > _maxBulkCreation)
|
||||
{
|
||||
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
|
||||
}
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
|
||||
var policies = request.ToBaseAccessPoliciesForServiceAccount(id);
|
||||
await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient);
|
||||
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id);
|
||||
return new ServiceAccountAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/access-policies")]
|
||||
public async Task<ServiceAccountAccessPoliciesResponseModel> GetServiceAccountAccessPoliciesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
|
||||
|
||||
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id);
|
||||
return new ServiceAccountAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
|
||||
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
|
||||
var results = await _accessPolicyRepository.GetManyByServiceAccountIdAsync(id, userId, accessClient);
|
||||
var responses = results.Select(ap =>
|
||||
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
|
||||
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPost("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
|
||||
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
|
||||
[FromBody] List<GrantedAccessPolicyRequest> requests)
|
||||
{
|
||||
if (requests.Count > _maxBulkCreation)
|
||||
{
|
||||
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
|
||||
}
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
|
||||
var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id));
|
||||
var results =
|
||||
await _createAccessPoliciesCommand.CreateManyAsync(new List<BaseAccessPolicy>(policies), userId, accessClient);
|
||||
var responses = results.Select(ap =>
|
||||
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
|
||||
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id,
|
||||
[FromBody] AccessPolicyUpdateRequest request)
|
||||
{
|
||||
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write, userId);
|
||||
|
||||
return result switch
|
||||
{
|
||||
UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy),
|
||||
UserServiceAccountAccessPolicy accessPolicy =>
|
||||
new UserServiceAccountAccessPolicyResponseModel(accessPolicy),
|
||||
GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy),
|
||||
GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel(
|
||||
accessPolicy),
|
||||
ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel(
|
||||
accessPolicy),
|
||||
_ => throw new ArgumentException("Unsupported access policy type provided.")
|
||||
_ => throw new ArgumentException("Unsupported access policy type provided."),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task DeleteAccessPolicyAsync([FromRoute] Guid id)
|
||||
{
|
||||
await _deleteAccessPolicyCommand.DeleteAsync(id);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _deleteAccessPolicyCommand.DeleteAsync(id, userId);
|
||||
}
|
||||
|
||||
[HttpGet("/organizations/{id}/access-policies/people/potential-grantees")]
|
||||
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetPeoplePotentialGranteesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g));
|
||||
|
||||
var organizationUsers =
|
||||
await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var userResponses = organizationUsers
|
||||
.Where(user => user.AccessSecretsManager)
|
||||
.Select(userDetails => new PotentialGranteeResponseModel(userDetails));
|
||||
|
||||
return new ListResponseModel<PotentialGranteeResponseModel>(userResponses.Concat(groupResponses));
|
||||
}
|
||||
|
||||
[HttpGet("/organizations/{id}/access-policies/service-accounts/potential-grantees")]
|
||||
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetServiceAccountsPotentialGranteesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(id);
|
||||
|
||||
var serviceAccounts =
|
||||
await _serviceAccountRepository.GetManyByOrganizationIdWriteAccessAsync(id,
|
||||
userId,
|
||||
accessClient);
|
||||
var serviceAccountResponses =
|
||||
serviceAccounts.Select(serviceAccount => new PotentialGranteeResponseModel(serviceAccount));
|
||||
|
||||
return new ListResponseModel<PotentialGranteeResponseModel>(serviceAccountResponses);
|
||||
}
|
||||
|
||||
[HttpGet("/organizations/{id}/access-policies/projects/potential-grantees")]
|
||||
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetProjectPotentialGranteesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(id);
|
||||
|
||||
var projects =
|
||||
await _projectRepository.GetManyByOrganizationIdWriteAccessAsync(id,
|
||||
userId,
|
||||
accessClient);
|
||||
var projectResponses =
|
||||
projects.Select(project => new PotentialGranteeResponseModel(project));
|
||||
|
||||
return new ListResponseModel<PotentialGranteeResponseModel>(projectResponses);
|
||||
}
|
||||
|
||||
private async Task CheckUserHasWriteAccessToProjectAsync(Project project)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId);
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount)
|
||||
{
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
|
||||
serviceAccount.Id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientTypeAsync(Guid organizationId)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
return (accessClient, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ public class ProjectsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId), userId);
|
||||
return new ProjectResponseModel(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -15,23 +19,32 @@ namespace Bit.Api.SecretsManager.Controllers;
|
||||
public class SecretsController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly ICreateSecretCommand _createSecretCommand;
|
||||
private readonly IUpdateSecretCommand _updateSecretCommand;
|
||||
private readonly IDeleteSecretCommand _deleteSecretCommand;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEventService _eventService;
|
||||
|
||||
public SecretsController(
|
||||
ICurrentContext currentContext,
|
||||
IProjectRepository projectRepository,
|
||||
ISecretRepository secretRepository,
|
||||
ICreateSecretCommand createSecretCommand,
|
||||
IUpdateSecretCommand updateSecretCommand,
|
||||
IDeleteSecretCommand deleteSecretCommand)
|
||||
IDeleteSecretCommand deleteSecretCommand,
|
||||
IUserService userService,
|
||||
IEventService eventService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_projectRepository = projectRepository;
|
||||
_secretRepository = secretRepository;
|
||||
_createSecretCommand = createSecretCommand;
|
||||
_updateSecretCommand = updateSecretCommand;
|
||||
_deleteSecretCommand = deleteSecretCommand;
|
||||
_userService = userService;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
[HttpGet("organizations/{organizationId}/secrets")]
|
||||
@@ -42,7 +55,12 @@ public class SecretsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, accessClient);
|
||||
|
||||
return new SecretWithProjectsListResponseModel(secrets);
|
||||
}
|
||||
|
||||
@@ -54,7 +72,8 @@ public class SecretsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId), userId);
|
||||
return new SecretResponseModel(result);
|
||||
}
|
||||
|
||||
@@ -62,34 +81,81 @@ public class SecretsController : Controller
|
||||
public async Task<SecretResponseModel> GetAsync([FromRoute] Guid id)
|
||||
{
|
||||
var secret = await _secretRepository.GetByIdAsync(id);
|
||||
if (secret == null)
|
||||
|
||||
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await UserHasReadAccessToSecret(secret))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (_currentContext.ClientType == ClientType.ServiceAccount)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved);
|
||||
}
|
||||
|
||||
return new SecretResponseModel(secret);
|
||||
}
|
||||
|
||||
[HttpGet("projects/{projectId}/secrets")]
|
||||
public async Task<SecretWithProjectsListResponseModel> GetSecretsByProjectAsync([FromRoute] Guid projectId)
|
||||
{
|
||||
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId);
|
||||
var responses = secrets.Select(s => new SecretResponseModel(s));
|
||||
var project = await _projectRepository.GetByIdAsync(projectId);
|
||||
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId, userId, accessClient);
|
||||
|
||||
return new SecretWithProjectsListResponseModel(secrets);
|
||||
}
|
||||
|
||||
[HttpPut("secrets/{id}")]
|
||||
public async Task<SecretResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
|
||||
public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
|
||||
{
|
||||
var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var secret = updateRequest.ToSecret(id);
|
||||
var result = await _updateSecretCommand.UpdateAsync(secret, userId);
|
||||
return new SecretResponseModel(result);
|
||||
}
|
||||
|
||||
// TODO Once permissions are setup for Secrets Manager need to enforce them on delete.
|
||||
[HttpPost("secrets/delete")]
|
||||
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
|
||||
{
|
||||
var results = await _deleteSecretCommand.DeleteSecrets(ids);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var results = await _deleteSecretCommand.DeleteSecrets(ids, userId);
|
||||
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
|
||||
return new ListResponseModel<BulkDeleteResponseModel>(responses);
|
||||
}
|
||||
|
||||
public async Task<bool> UserHasReadAccessToSecret(Secret secret)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
var hasAccess = orgAdmin;
|
||||
|
||||
if (secret.Projects?.Count > 0)
|
||||
{
|
||||
Guid projectId = secret.Projects.FirstOrDefault().Id;
|
||||
hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _projectRepository.UserHasReadAccessToProject(projectId, userId),
|
||||
AccessClientType.ServiceAccount => await _projectRepository.ServiceAccountHasReadAccessToProject(projectId, userId),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
return hasAccess;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Controllers;
|
||||
|
||||
[SecretsManager]
|
||||
public class SecretsManagerPortingController : Controller
|
||||
{
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IImportCommand _importCommand;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public SecretsManagerPortingController(ISecretRepository secretRepository, IProjectRepository projectRepository, IUserService userService, IImportCommand importCommand, ICurrentContext currentContext)
|
||||
{
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_userService = userService;
|
||||
_importCommand = importCommand;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
[HttpGet("sm/{organizationId}/export")]
|
||||
public async Task<SMExportResponseModel> Export([FromRoute] Guid organizationId, [FromRoute] string format = "json")
|
||||
{
|
||||
if (!await _currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
|
||||
|
||||
if (projects == null && secrets == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new SMExportResponseModel(projects, secrets);
|
||||
}
|
||||
|
||||
[HttpPost("sm/{organizationId}/import")]
|
||||
public async Task Import([FromRoute] Guid organizationId, [FromBody] SMImportRequestModel importRequest)
|
||||
{
|
||||
if (!await _currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (importRequest.Projects?.Count() > 1000 || importRequest.Secrets?.Count() > 6000)
|
||||
{
|
||||
throw new BadRequestException("You cannot import this much data at once, the limit is 1000 projects and 6000 secrets.");
|
||||
}
|
||||
|
||||
await _importCommand.ImportAsync(organizationId, importRequest.ToSMImport());
|
||||
}
|
||||
}
|
||||
80
src/Api/SecretsManager/Controllers/SecretsTrashController.cs
Normal file
80
src/Api/SecretsManager/Controllers/SecretsTrashController.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Controllers;
|
||||
|
||||
[SecretsManager]
|
||||
[Authorize("secrets")]
|
||||
public class TrashController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IEmptyTrashCommand _emptyTrashCommand;
|
||||
private readonly IRestoreTrashCommand _restoreTrashCommand;
|
||||
|
||||
public TrashController(
|
||||
ICurrentContext currentContext,
|
||||
ISecretRepository secretRepository,
|
||||
IEmptyTrashCommand emptyTrashCommand,
|
||||
IRestoreTrashCommand restoreTrashCommand)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_secretRepository = secretRepository;
|
||||
_emptyTrashCommand = emptyTrashCommand;
|
||||
_restoreTrashCommand = restoreTrashCommand;
|
||||
}
|
||||
|
||||
[HttpGet("secrets/{organizationId}/trash")]
|
||||
public async Task<SecretWithProjectsListResponseModel> ListByOrganizationAsync(Guid organizationId)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await _currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var secrets = await _secretRepository.GetManyByOrganizationIdInTrashAsync(organizationId);
|
||||
return new SecretWithProjectsListResponseModel(secrets);
|
||||
}
|
||||
|
||||
[HttpPost("secrets/{organizationId}/trash/empty")]
|
||||
public async Task EmptyTrashAsync(Guid organizationId, [FromBody] List<Guid> ids)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await _currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
await _emptyTrashCommand.EmptyTrash(organizationId, ids);
|
||||
}
|
||||
|
||||
[HttpPost("secrets/{organizationId}/trash/restore")]
|
||||
public async Task RestoreTrashAsync(Guid organizationId, [FromBody] List<Guid> ids)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await _currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
await _restoreTrashCommand.RestoreTrash(organizationId, ids);
|
||||
}
|
||||
}
|
||||
@@ -19,20 +19,23 @@ namespace Bit.Api.SecretsManager.Controllers;
|
||||
public class ServiceAccountsController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IApiKeyRepository _apiKeyRepository;
|
||||
private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
|
||||
private readonly ICreateServiceAccountCommand _createServiceAccountCommand;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
|
||||
|
||||
public ServiceAccountsController(
|
||||
ICurrentContext currentContext,
|
||||
IUserService userService,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IApiKeyRepository apiKeyRepository,
|
||||
ICreateAccessTokenCommand createAccessTokenCommand,
|
||||
IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand,
|
||||
IUpdateServiceAccountCommand updateServiceAccountCommand)
|
||||
ICreateServiceAccountCommand createServiceAccountCommand,
|
||||
IUpdateServiceAccountCommand updateServiceAccountCommand,
|
||||
IRevokeAccessTokensCommand revokeAccessTokensCommand)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
@@ -40,6 +43,7 @@ public class ServiceAccountsController : Controller
|
||||
_apiKeyRepository = apiKeyRepository;
|
||||
_createServiceAccountCommand = createServiceAccountCommand;
|
||||
_updateServiceAccountCommand = updateServiceAccountCommand;
|
||||
_revokeAccessTokensCommand = revokeAccessTokensCommand;
|
||||
_createAccessTokenCommand = createAccessTokenCommand;
|
||||
}
|
||||
|
||||
@@ -129,4 +133,37 @@ public class ServiceAccountsController : Controller
|
||||
var result = await _createAccessTokenCommand.CreateAsync(request.ToApiKey(id), userId);
|
||||
return new AccessTokenCreationResponseModel(result);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/access-tokens/revoke")]
|
||||
public async Task RevokeAccessTokensAsync(Guid id, [FromBody] RevokeAccessTokensRequest request)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _revokeAccessTokensCommand.RevokeAsync(serviceAccount, request.Ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class AccessPoliciesCreateRequest
|
||||
|
||||
public IEnumerable<AccessPolicyRequest>? ServiceAccountAccessPolicyRequests { get; set; }
|
||||
|
||||
public List<BaseAccessPolicy> ToBaseAccessPoliciesForProject(Guid projectId)
|
||||
public List<BaseAccessPolicy> ToBaseAccessPoliciesForProject(Guid grantedProjectId)
|
||||
{
|
||||
if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null)
|
||||
{
|
||||
@@ -21,20 +21,77 @@ public class AccessPoliciesCreateRequest
|
||||
}
|
||||
|
||||
var userAccessPolicies = UserAccessPolicyRequests?
|
||||
.Select(x => x.ToUserProjectAccessPolicy(projectId)).ToList();
|
||||
.Select(x => x.ToUserProjectAccessPolicy(grantedProjectId)).ToList();
|
||||
|
||||
var groupAccessPolicies = GroupAccessPolicyRequests?
|
||||
.Select(x => x.ToGroupProjectAccessPolicy(projectId)).ToList();
|
||||
.Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId)).ToList();
|
||||
|
||||
var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests?
|
||||
.Select(x => x.ToServiceAccountProjectAccessPolicy(projectId)).ToList();
|
||||
.Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId)).ToList();
|
||||
|
||||
var policies = new List<BaseAccessPolicy>();
|
||||
if (userAccessPolicies != null) { policies.AddRange(userAccessPolicies); }
|
||||
if (groupAccessPolicies != null) { policies.AddRange(groupAccessPolicies); }
|
||||
if (serviceAccountAccessPolicies != null) { policies.AddRange(serviceAccountAccessPolicies); }
|
||||
if (userAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(userAccessPolicies);
|
||||
}
|
||||
|
||||
if (groupAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(groupAccessPolicies);
|
||||
}
|
||||
|
||||
if (serviceAccountAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(serviceAccountAccessPolicies);
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
public List<BaseAccessPolicy> ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId)
|
||||
{
|
||||
if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null)
|
||||
{
|
||||
throw new BadRequestException("No creation requests provided.");
|
||||
}
|
||||
|
||||
var userAccessPolicies = UserAccessPolicyRequests?
|
||||
.Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId)).ToList();
|
||||
|
||||
var groupAccessPolicies = GroupAccessPolicyRequests?
|
||||
.Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId)).ToList();
|
||||
|
||||
var policies = new List<BaseAccessPolicy>();
|
||||
if (userAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(userAccessPolicies);
|
||||
}
|
||||
|
||||
if (groupAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(groupAccessPolicies);
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
var total = 0;
|
||||
|
||||
if (UserAccessPolicyRequests != null)
|
||||
{
|
||||
total += UserAccessPolicyRequests.Count();
|
||||
}
|
||||
if (GroupAccessPolicyRequests != null)
|
||||
{
|
||||
total += GroupAccessPolicyRequests.Count();
|
||||
}
|
||||
if (ServiceAccountAccessPolicyRequests != null)
|
||||
{
|
||||
total += ServiceAccountAccessPolicyRequests.Count();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public class AccessPolicyRequest
|
||||
@@ -74,4 +131,22 @@ public class AccessPolicyRequest
|
||||
Read = Read,
|
||||
Write = Write
|
||||
};
|
||||
|
||||
public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id) =>
|
||||
new()
|
||||
{
|
||||
OrganizationUserId = GranteeId,
|
||||
GrantedServiceAccountId = id,
|
||||
Read = Read,
|
||||
Write = Write
|
||||
};
|
||||
|
||||
public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id) =>
|
||||
new()
|
||||
{
|
||||
GroupId = GranteeId,
|
||||
GrantedServiceAccountId = id,
|
||||
Read = Read,
|
||||
Write = Write
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Request;
|
||||
|
||||
public class GrantedAccessPolicyRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid GrantedId { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool Read { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool Write { get; set; }
|
||||
|
||||
public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid serviceAccountId) =>
|
||||
new()
|
||||
{
|
||||
ServiceAccountId = serviceAccountId,
|
||||
GrantedProjectId = GrantedId,
|
||||
Read = Read,
|
||||
Write = Write,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class RevokeAccessTokensRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid[] Ids { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.SecretsManager.Commands.Porting;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Request;
|
||||
|
||||
public class SMImportRequestModel
|
||||
{
|
||||
public IEnumerable<InnerProjectImportRequestModel> Projects { get; set; }
|
||||
public IEnumerable<InnerSecretImportRequestModel> Secrets { get; set; }
|
||||
|
||||
public class InnerProjectImportRequestModel
|
||||
{
|
||||
public InnerProjectImportRequestModel() { }
|
||||
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[EncryptedString]
|
||||
[EncryptedStringLength(1000)]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class InnerSecretImportRequestModel
|
||||
{
|
||||
public InnerSecretImportRequestModel() { }
|
||||
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[EncryptedString]
|
||||
[EncryptedStringLength(1000)]
|
||||
public string Key { get; set; }
|
||||
|
||||
[Required]
|
||||
[EncryptedString]
|
||||
[EncryptedStringLength(1000)]
|
||||
public string Value { get; set; }
|
||||
|
||||
[Required]
|
||||
[EncryptedString]
|
||||
[EncryptedStringLength(1000)]
|
||||
public string Note { get; set; }
|
||||
|
||||
[Required]
|
||||
public IEnumerable<Guid> ProjectIds { get; set; }
|
||||
}
|
||||
|
||||
public SMImport ToSMImport()
|
||||
{
|
||||
return new SMImport
|
||||
{
|
||||
Projects = Projects?.Select(p => new SMImport.InnerProject
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
}),
|
||||
Secrets = Secrets?.Select(s => new SMImport.InnerSecret
|
||||
{
|
||||
Id = s.Id,
|
||||
Key = s.Key,
|
||||
Value = s.Value,
|
||||
Note = s.Note,
|
||||
ProjectIds = s.ProjectIds,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
@@ -20,6 +21,11 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel
|
||||
public bool Write { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
public string? GetUserDisplayName(User? user)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(user?.Name) ? user?.Email : user?.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
@@ -30,7 +36,7 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
OrganizationUserName = accessPolicy.User?.Name;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
}
|
||||
|
||||
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
|
||||
@@ -51,7 +57,7 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
|
||||
OrganizationUserName = accessPolicy.User?.Name;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
}
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
|
||||
@@ -115,6 +121,7 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe
|
||||
ServiceAccountId = accessPolicy.ServiceAccountId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
|
||||
GrantedProjectName = accessPolicy.GrantedProject?.Name;
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel()
|
||||
@@ -125,4 +132,5 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public string? ServiceAccountName { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public string? GrantedProjectName { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class PotentialGranteeResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "potentialGrantee";
|
||||
|
||||
public PotentialGranteeResponseModel(Group group)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (group == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(group));
|
||||
}
|
||||
|
||||
Id = group.Id.ToString();
|
||||
Name = group.Name;
|
||||
Type = "group";
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel(OrganizationUserUserDetails user)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
Id = user.Id.ToString();
|
||||
Name = user.Name;
|
||||
Email = user.Email;
|
||||
Type = "user";
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel(ServiceAccount serviceAccount)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceAccount));
|
||||
}
|
||||
|
||||
Id = serviceAccount.Id.ToString();
|
||||
Name = serviceAccount.Name;
|
||||
Type = "serviceAccount";
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel(Project project)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(project));
|
||||
}
|
||||
|
||||
Id = project.Id.ToString();
|
||||
Name = project.Name;
|
||||
Type = "project";
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
public string? Email { get; set; }
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel
|
||||
: base(_objectName)
|
||||
{
|
||||
foreach (var baseAccessPolicy in baseAccessPolicies)
|
||||
{
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserProjectAccessPolicy accessPolicy:
|
||||
@@ -24,6 +25,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel
|
||||
new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProjectAccessPoliciesResponseModel() : base(_objectName)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class SMExportResponseModel : ResponseModel
|
||||
{
|
||||
public SMExportResponseModel(IEnumerable<Project> projects, IEnumerable<Secret> secrets, string obj = "SecretsManagerExportResponseModel") : base(obj)
|
||||
{
|
||||
Secrets = secrets?.Select(s => new InnerSecretExportResponseModel(s));
|
||||
Projects = projects?.Select(p => new InnerProjectExportResponseModel(p));
|
||||
}
|
||||
|
||||
public IEnumerable<InnerProjectExportResponseModel> Projects { get; set; }
|
||||
public IEnumerable<InnerSecretExportResponseModel> Secrets { get; set; }
|
||||
|
||||
public class InnerProjectExportResponseModel
|
||||
{
|
||||
public InnerProjectExportResponseModel(Project project)
|
||||
{
|
||||
Id = project.Id;
|
||||
Name = project.Name;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class InnerSecretExportResponseModel
|
||||
{
|
||||
public InnerSecretExportResponseModel(Secret secret)
|
||||
{
|
||||
Id = secret.Id;
|
||||
Key = secret.Key;
|
||||
Value = secret.Value;
|
||||
Note = secret.Note;
|
||||
ProjectIds = secret.Projects?.Select(p => p.Id);
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Note { get; set; }
|
||||
public IEnumerable<Guid> ProjectIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Commands.Porting;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class SMImportResponseModel : ResponseModel
|
||||
{
|
||||
public SMImportResponseModel(SMImport import, string obj = "SecretsManagerImportResponseModel") : base(obj)
|
||||
{
|
||||
Projects = import.Projects?.Select(p => new InnerProjectImportResponseModel(p));
|
||||
Secrets = import.Secrets?.Select(s => new InnerSecretImportResponseModel(s));
|
||||
}
|
||||
|
||||
public IEnumerable<InnerProjectImportResponseModel> Projects { get; set; }
|
||||
public IEnumerable<InnerSecretImportResponseModel> Secrets { get; set; }
|
||||
|
||||
public class InnerProjectImportResponseModel
|
||||
{
|
||||
public InnerProjectImportResponseModel() { }
|
||||
|
||||
public InnerProjectImportResponseModel(SMImport.InnerProject project)
|
||||
{
|
||||
Id = project.Id;
|
||||
Name = project.Name;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class InnerSecretImportResponseModel
|
||||
{
|
||||
public InnerSecretImportResponseModel() { }
|
||||
|
||||
public InnerSecretImportResponseModel(SMImport.InnerSecret secret)
|
||||
{
|
||||
Id = secret.Id;
|
||||
Key = secret.Key;
|
||||
Value = secret.Value;
|
||||
Note = secret.Note;
|
||||
ProjectIds = secret.ProjectIds;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Note { get; set; }
|
||||
public IEnumerable<Guid> ProjectIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "serviceAccountAccessPolicies";
|
||||
|
||||
public ServiceAccountAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (baseAccessPolicies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var baseAccessPolicy in baseAccessPolicies)
|
||||
{
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserServiceAccountAccessPolicy accessPolicy:
|
||||
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
case GroupServiceAccountAccessPolicy accessPolicy:
|
||||
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceAccountAccessPoliciesResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public List<UserServiceAccountAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
|
||||
public List<GroupServiceAccountAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
}
|
||||
@@ -92,6 +92,11 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute
|
||||
errorMessage = "Unauthorized.";
|
||||
context.HttpContext.Response.StatusCode = 401;
|
||||
}
|
||||
else if (exception is ConflictException)
|
||||
{
|
||||
errorMessage = exception.Message;
|
||||
context.HttpContext.Response.StatusCode = 409;
|
||||
}
|
||||
else if (exception is AggregateException aggregateException)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = 400;
|
||||
|
||||
@@ -85,8 +85,8 @@
|
||||
},
|
||||
"Azure.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.24.0",
|
||||
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
|
||||
"resolved": "1.25.0",
|
||||
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
|
||||
"System.Diagnostics.DiagnosticSource": "4.6.0",
|
||||
@@ -123,28 +123,28 @@
|
||||
},
|
||||
"Azure.Storage.Blobs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.11.0",
|
||||
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
|
||||
"resolved": "12.14.1",
|
||||
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.10.0",
|
||||
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
|
||||
"resolved": "12.13.0",
|
||||
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
|
||||
"dependencies": {
|
||||
"Azure.Core": "1.22.0",
|
||||
"Azure.Core": "1.25.0",
|
||||
"System.IO.Hashing": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Queues": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.9.0",
|
||||
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
|
||||
"resolved": "12.12.0",
|
||||
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Memory.Data": "1.0.2",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
@@ -171,6 +171,14 @@
|
||||
"resolved": "2.0.123",
|
||||
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
|
||||
},
|
||||
"DnsClient": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Fido2": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1",
|
||||
@@ -2794,10 +2802,11 @@
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
|
||||
"Azure.Storage.Blobs": "[12.11.0, )",
|
||||
"Azure.Storage.Queues": "[12.9.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
},
|
||||
"Azure.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.24.0",
|
||||
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
|
||||
"resolved": "1.25.0",
|
||||
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
|
||||
"System.Diagnostics.DiagnosticSource": "4.6.0",
|
||||
@@ -112,28 +112,28 @@
|
||||
},
|
||||
"Azure.Storage.Blobs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.11.0",
|
||||
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
|
||||
"resolved": "12.14.1",
|
||||
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.10.0",
|
||||
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
|
||||
"resolved": "12.13.0",
|
||||
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
|
||||
"dependencies": {
|
||||
"Azure.Core": "1.22.0",
|
||||
"Azure.Core": "1.25.0",
|
||||
"System.IO.Hashing": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Queues": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.9.0",
|
||||
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
|
||||
"resolved": "12.12.0",
|
||||
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"Azure.Storage.Common": "12.13.0",
|
||||
"System.Memory.Data": "1.0.2",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
@@ -160,6 +160,14 @@
|
||||
"resolved": "2.0.123",
|
||||
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
|
||||
},
|
||||
"DnsClient": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Fido2": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1",
|
||||
@@ -3245,10 +3253,11 @@
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
|
||||
"Azure.Storage.Blobs": "[12.11.0, )",
|
||||
"Azure.Storage.Queues": "[12.9.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
|
||||
@@ -35,6 +35,7 @@ public class CurrentContext : ICurrentContext
|
||||
public virtual string ClientId { get; set; }
|
||||
public virtual Version ClientVersion { get; set; }
|
||||
public virtual ClientType ClientType { get; set; }
|
||||
public virtual Guid? ServiceAccountOrganizationId { get; set; }
|
||||
|
||||
public CurrentContext(IProviderUserRepository providerUserRepository)
|
||||
{
|
||||
@@ -146,6 +147,11 @@ public class CurrentContext : ICurrentContext
|
||||
ClientType = c;
|
||||
}
|
||||
|
||||
if (ClientType == ClientType.ServiceAccount)
|
||||
{
|
||||
ServiceAccountOrganizationId = new Guid(GetClaimValue(claimsDict, Claims.Organization));
|
||||
}
|
||||
|
||||
DeviceIdentifier = GetClaimValue(claimsDict, Claims.Device);
|
||||
|
||||
Organizations = GetOrganizations(claimsDict, orgApi);
|
||||
@@ -445,6 +451,11 @@ public class CurrentContext : ICurrentContext
|
||||
|
||||
public bool AccessSecretsManager(Guid orgId)
|
||||
{
|
||||
if (ServiceAccountOrganizationId.HasValue && ServiceAccountOrganizationId.Value == orgId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Organizations?.Any(o => o.Id == orgId && o.AccessSecretsManager) ?? false;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.0.150" />
|
||||
<PackageReference Include="AWSSDK.SQS" Version="3.7.2.47" />
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.2.1" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.11.0" />
|
||||
<PackageReference Include="Azure.Storage.Queues" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
|
||||
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
|
||||
<PackageReference Include="BitPay.Light" Version="1.0.1907" />
|
||||
<PackageReference Include="DnsClient" Version="1.7.0" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
|
||||
|
||||
@@ -28,6 +28,9 @@ public class Event : ITableObject<Guid>, IEvent
|
||||
IpAddress = e.IpAddress;
|
||||
ActingUserId = e.ActingUserId;
|
||||
SystemUser = e.SystemUser;
|
||||
DomainName = e.DomainName;
|
||||
SecretId = e.SecretId;
|
||||
ServiceAccountId = e.ServiceAccountId;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
@@ -49,6 +52,9 @@ public class Event : ITableObject<Guid>, IEvent
|
||||
public string IpAddress { get; set; }
|
||||
public Guid? ActingUserId { get; set; }
|
||||
public EventSystemUser? SystemUser { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Entities;
|
||||
@@ -198,4 +199,33 @@ public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorabl
|
||||
|
||||
return providers[provider];
|
||||
}
|
||||
|
||||
public void UpdateFromLicense(OrganizationLicense license)
|
||||
{
|
||||
Name = license.Name;
|
||||
BusinessName = license.BusinessName;
|
||||
BillingEmail = license.BillingEmail;
|
||||
PlanType = license.PlanType;
|
||||
Seats = license.Seats;
|
||||
MaxCollections = license.MaxCollections;
|
||||
UseGroups = license.UseGroups;
|
||||
UseDirectory = license.UseDirectory;
|
||||
UseEvents = license.UseEvents;
|
||||
UseTotp = license.UseTotp;
|
||||
Use2fa = license.Use2fa;
|
||||
UseApi = license.UseApi;
|
||||
UsePolicies = license.UsePolicies;
|
||||
UseSso = license.UseSso;
|
||||
UseKeyConnector = license.UseKeyConnector;
|
||||
UseScim = license.UseScim;
|
||||
UseResetPassword = license.UseResetPassword;
|
||||
SelfHost = license.SelfHost;
|
||||
UsersGetPremium = license.UsersGetPremium;
|
||||
UseCustomPermissions = license.UseCustomPermissions;
|
||||
Plan = license.Plan;
|
||||
Enabled = license.Enabled;
|
||||
ExpirationDate = license.Expires;
|
||||
LicenseKey = license.LicenseKey;
|
||||
RevisionDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
48
src/Core/Entities/OrganizationDomain.cs
Normal file
48
src/Core/Entities/OrganizationDomain.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Entities;
|
||||
|
||||
public class OrganizationDomain : ITableObject<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string Txt { get; set; }
|
||||
[MaxLength(255)]
|
||||
public string DomainName { get; set; }
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? VerifiedDate { get; private set; }
|
||||
public DateTime NextRunDate { get; private set; }
|
||||
public DateTime? LastCheckedDate { get; private set; }
|
||||
public int JobRunCount { get; private set; }
|
||||
public void SetNewId() => Id = CoreHelpers.GenerateComb();
|
||||
|
||||
public void SetNextRunDate(int interval)
|
||||
{
|
||||
//verification can take up to 72 hours
|
||||
//1st job runs after 12hrs, 2nd after 24hrs and 3rd after 36hrs
|
||||
NextRunDate = JobRunCount == 0
|
||||
? CreationDate.AddHours(interval)
|
||||
: NextRunDate.AddHours((JobRunCount + 1) * interval);
|
||||
}
|
||||
|
||||
public void SetJobRunCount()
|
||||
{
|
||||
if (JobRunCount == 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JobRunCount++;
|
||||
}
|
||||
|
||||
public void SetVerifiedDate()
|
||||
{
|
||||
VerifiedDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void SetLastCheckedDate()
|
||||
{
|
||||
LastCheckedDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,6 @@ public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscri
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
public int FailedLoginCount { get; set; }
|
||||
public DateTime? LastFailedLoginDate { get; set; }
|
||||
public bool UnknownDeviceVerificationEnabled { get; set; }
|
||||
[MaxLength(7)]
|
||||
public string AvatarColor { get; set; }
|
||||
public DateTime? LastPasswordChangeDate { get; set; }
|
||||
|
||||
@@ -48,4 +48,6 @@ public enum DeviceType : byte
|
||||
SafariExtension = 20,
|
||||
[Display(Name = "SDK")]
|
||||
SDK = 21,
|
||||
[Display(Name = "Server")]
|
||||
Server = 22
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
public enum EventSystemUser : byte
|
||||
{
|
||||
SCIM = 1
|
||||
SCIM = 1,
|
||||
DomainVerification = 2
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Bit.Core.Enums;
|
||||
|
||||
// Increment by 100 for each new set of events
|
||||
public enum EventType : int
|
||||
{
|
||||
User_LoggedIn = 1000,
|
||||
@@ -75,4 +76,11 @@ public enum EventType : int
|
||||
ProviderOrganization_Added = 1901,
|
||||
ProviderOrganization_Removed = 1902,
|
||||
ProviderOrganization_VaultAccessed = 1903,
|
||||
|
||||
OrganizationDomain_Added = 2000,
|
||||
OrganizationDomain_Removed = 2001,
|
||||
OrganizationDomain_Verified = 2002,
|
||||
OrganizationDomain_NotVerified = 2003,
|
||||
|
||||
Secret_Retrieved = 2100,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
namespace Bit.Core.Exceptions;
|
||||
|
||||
public class ConflictException : Exception { }
|
||||
public class ConflictException : Exception
|
||||
{
|
||||
public ConflictException() : base("Conflict.") { }
|
||||
public ConflictException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
7
src/Core/Exceptions/DnsQueryException.cs
Normal file
7
src/Core/Exceptions/DnsQueryException.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Exceptions;
|
||||
|
||||
public class DnsQueryException : Exception
|
||||
{
|
||||
public DnsQueryException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
10
src/Core/Exceptions/DomainClaimedException.cs
Normal file
10
src/Core/Exceptions/DomainClaimedException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Bit.Core.Exceptions;
|
||||
|
||||
public class DomainClaimedException : Exception
|
||||
{
|
||||
public DomainClaimedException()
|
||||
: base("The domain is not available to be claimed.")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
10
src/Core/Exceptions/DomainVerifiedException.cs
Normal file
10
src/Core/Exceptions/DomainVerifiedException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Bit.Core.Exceptions;
|
||||
|
||||
public class DomainVerifiedException : Exception
|
||||
{
|
||||
public DomainVerifiedException()
|
||||
: base("Domain has already been verified.")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
10
src/Core/Exceptions/DuplicateDomainException.cs
Normal file
10
src/Core/Exceptions/DuplicateDomainException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Bit.Core.Exceptions;
|
||||
|
||||
public class DuplicateDomainException : Exception
|
||||
{
|
||||
public DuplicateDomainException()
|
||||
: base("A domain already exists for this organization.")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{{#>FullHtmlLayout}}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
Your two-step verification code is: <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{Token}}</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
Use this code to complete logging in with Bitwarden.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
|
||||
This email was sent because you are logging in from a device we don’t recognize. If you did not request this code, you may want to <a target="_blank" clicktracking=off href="https://bitwarden.com/help/master-password/#change-master-password" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #175DDC; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; text-decoration: underline;">change your master password</a>. You can view our tips for selecting a secure master password <a target="_blank" clicktracking=off href="https://bitwarden.com/blog/picking-the-right-password-for-your-password-manager/" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #175DDC; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; text-decoration: underline;">here</a>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{/FullHtmlLayout}}
|
||||
@@ -1,7 +0,0 @@
|
||||
{{#>BasicTextLayout}}
|
||||
Your two-step verification code is: {{Token}}
|
||||
|
||||
Use this code to complete logging in with Bitwarden.
|
||||
|
||||
This email was sent because you are logging in from a device we don’t recognize. If you did not request this code, you may want to change your master password (https://bitwarden.com/help/master-password/#change-master-password). You can view our tips for selecting a secure master password here (https://bitwarden.com/blog/picking-the-right-password-for-your-password-manager/).
|
||||
{{/BasicTextLayout}}
|
||||
@@ -0,0 +1,27 @@
|
||||
{{#>FullHtmlLayout}}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: left;" valign="top">
|
||||
The domain {{DomainName}} in your Bitwarden organization could not be verified.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
Check the corresponding record in your domain host. Then reverify this domain in Bitwarden to use it for your organization.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||
The domain will be removed from your organization in 7 days if it is not verified.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||
<a href="{{{Url}}}" clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
Manage Domains
|
||||
</a>
|
||||
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{/FullHtmlLayout}}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{#>BasicTextLayout}}
|
||||
The domain {{DomainName}} in your Bitwarden organization could not be verified.
|
||||
|
||||
Check the corresponding record in your domain host. Then reverify this domain in Bitwarden to use it for your organization.
|
||||
|
||||
The domain will be removed from your organization in 7 days if it is not verified.
|
||||
|
||||
{{Url}}
|
||||
|
||||
{{/BasicTextLayout}}
|
||||
27
src/Core/Models/Api/Response/Duo/DuoResponseModel.cs
Normal file
27
src/Core/Models/Api/Response/Duo/DuoResponseModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bit.Core.Models.Api.Response.Duo;
|
||||
|
||||
public class DuoResponseModel
|
||||
{
|
||||
[JsonPropertyName("stat")]
|
||||
public string Stat { get; set; }
|
||||
|
||||
[JsonPropertyName("code")]
|
||||
public int? Code { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonPropertyName("message_detail")]
|
||||
public string MessageDetail { get; set; }
|
||||
|
||||
[JsonPropertyName("response")]
|
||||
public Response Response { get; set; }
|
||||
}
|
||||
|
||||
public class Response
|
||||
{
|
||||
[JsonPropertyName("time")]
|
||||
public int Time { get; set; }
|
||||
}
|
||||
@@ -199,21 +199,42 @@ public class OrganizationLicense : ILicense
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanUse(IGlobalSettings globalSettings)
|
||||
public bool CanUse(IGlobalSettings globalSettings, ILicensingService licensingService, out string exception)
|
||||
{
|
||||
if (!Enabled || Issued > DateTime.UtcNow || Expires < DateTime.UtcNow)
|
||||
{
|
||||
exception = "Invalid license. Your organization is disabled or the license has expired.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ValidLicenseVersion)
|
||||
if (!ValidLicenseVersion)
|
||||
{
|
||||
return InstallationId == globalSettings.Installation.Id && SelfHost;
|
||||
exception = $"Version {Version} is not supported.";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
if (InstallationId != globalSettings.Installation.Id || !SelfHost)
|
||||
{
|
||||
throw new NotSupportedException($"Version {Version} is not supported.");
|
||||
exception = "Invalid license. Make sure your license allows for on-premise " +
|
||||
"hosting of organizations and that the installation id matches your current installation.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LicenseType != null && LicenseType != Enums.LicenseType.Organization)
|
||||
{
|
||||
exception = "Premium licenses cannot be applied to an organization. "
|
||||
+ "Upload this license from your personal account settings page.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!licensingService.VerifyLicense(this))
|
||||
{
|
||||
exception = "Invalid license.";
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool VerifyData(Organization organization, IGlobalSettings globalSettings)
|
||||
|
||||
@@ -32,4 +32,7 @@ public class EventMessage : IEvent
|
||||
public string IpAddress { get; set; }
|
||||
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
|
||||
public EventSystemUser? SystemUser { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ public class EventTableEntity : TableEntity, IEvent
|
||||
IpAddress = e.IpAddress;
|
||||
ActingUserId = e.ActingUserId;
|
||||
SystemUser = e.SystemUser;
|
||||
DomainName = e.DomainName;
|
||||
SecretId = e.SecretId;
|
||||
ServiceAccountId = e.ServiceAccountId;
|
||||
}
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
@@ -46,6 +49,9 @@ public class EventTableEntity : TableEntity, IEvent
|
||||
public string IpAddress { get; set; }
|
||||
public Guid? ActingUserId { get; set; }
|
||||
public EventSystemUser? SystemUser { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public Guid? SecretId { get; set; }
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
|
||||
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
|
||||
{
|
||||
@@ -152,6 +158,24 @@ public class EventTableEntity : TableEntity, IEvent
|
||||
});
|
||||
}
|
||||
|
||||
if (e.OrganizationId.HasValue && e.ServiceAccountId.HasValue)
|
||||
{
|
||||
entities.Add(new EventTableEntity(e)
|
||||
{
|
||||
PartitionKey = pKey,
|
||||
RowKey = $"ServiceAccountId={e.ServiceAccountId}__Date={dateKey}__Uniquifier={uniquifier}"
|
||||
});
|
||||
}
|
||||
|
||||
if (e.SecretId.HasValue)
|
||||
{
|
||||
entities.Add(new EventTableEntity(e)
|
||||
{
|
||||
PartitionKey = pKey,
|
||||
RowKey = $"SecretId={e.CipherId}__Date={dateKey}__Uniquifier={uniquifier}"
|
||||
});
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,7 @@ public interface IEvent
|
||||
string IpAddress { get; set; }
|
||||
DateTime Date { get; set; }
|
||||
EventSystemUser? SystemUser { get; set; }
|
||||
string DomainName { get; set; }
|
||||
Guid? SecretId { get; set; }
|
||||
Guid? ServiceAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Data.Organizations;
|
||||
|
||||
public class OrganizationDomainSsoDetailsData
|
||||
{
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string OrganizationName { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public bool SsoAvailable { get; set; }
|
||||
public string OrganizationIdentifier { get; set; }
|
||||
public bool SsoRequired { get; set; }
|
||||
public PolicyType? PolicyType { get; set; }
|
||||
public DateTime? VerifiedDate { get; set; }
|
||||
public bool OrganizationEnabled { get; set; }
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser
|
||||
public Guid? UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string AvatarColor { get; set; }
|
||||
public string TwoFactorProviders { get; set; }
|
||||
public bool? Premium { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
@@ -61,11 +62,9 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser
|
||||
return Premium.GetValueOrDefault(false);
|
||||
}
|
||||
|
||||
public bool OccupiesOrganizationSeat
|
||||
public Permissions GetPermissions()
|
||||
{
|
||||
get
|
||||
{
|
||||
return Status != OrganizationUserStatusType.Revoked;
|
||||
}
|
||||
return string.IsNullOrWhiteSpace(Permissions) ? null
|
||||
: CoreHelpers.LoadClassFromJsonData<Permissions>(Permissions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
|
||||
namespace Bit.Core.Models.Data.Organizations;
|
||||
|
||||
public class SelfHostedOrganizationDetails : Organization
|
||||
{
|
||||
public int OccupiedSeatCount { get; set; }
|
||||
public int CollectionCount { get; set; }
|
||||
public int GroupCount { get; set; }
|
||||
public IEnumerable<OrganizationUser> OrganizationUsers { get; set; }
|
||||
public IEnumerable<Policy> Policies { get; set; }
|
||||
public SsoConfig SsoConfig { get; set; }
|
||||
public IEnumerable<OrganizationConnection> ScimConnections { get; set; }
|
||||
|
||||
public bool CanUseLicense(OrganizationLicense license, out string exception)
|
||||
{
|
||||
if (license.Seats.HasValue && OccupiedSeatCount > license.Seats.Value)
|
||||
{
|
||||
exception = $"Your organization currently has {OccupiedSeatCount} seats filled. " +
|
||||
$"Your new license only has ({license.Seats.Value}) seats. Remove some users.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (license.MaxCollections.HasValue && CollectionCount > license.MaxCollections.Value)
|
||||
{
|
||||
exception = $"Your organization currently has {CollectionCount} collections. " +
|
||||
$"Your new license allows for a maximum of ({license.MaxCollections.Value}) collections. " +
|
||||
"Remove some collections.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseGroups && UseGroups && GroupCount > 1)
|
||||
{
|
||||
exception = $"Your organization currently has {GroupCount} groups. " +
|
||||
$"Your new license does not allow for the use of groups. Remove all groups.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var enabledPolicyCount = Policies.Count(p => p.Enabled);
|
||||
if (!license.UsePolicies && UsePolicies && enabledPolicyCount > 0)
|
||||
{
|
||||
exception = $"Your organization currently has {enabledPolicyCount} enabled " +
|
||||
$"policies. Your new license does not allow for the use of policies. Disable all policies.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseSso && UseSso && SsoConfig is { Enabled: true })
|
||||
{
|
||||
exception = $"Your organization currently has a SSO configuration. " +
|
||||
$"Your new license does not allow for the use of SSO. Disable your SSO configuration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseKeyConnector && UseKeyConnector && SsoConfig?.Data != null &&
|
||||
SsoConfig.GetData().KeyConnectorEnabled)
|
||||
{
|
||||
exception = $"Your organization currently has Key Connector enabled. " +
|
||||
$"Your new license does not allow for the use of Key Connector. Disable your Key Connector.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseScim && UseScim && ScimConnections != null &&
|
||||
ScimConnections.Any(c => c.GetConfig<ScimConfig>() is { Enabled: true }))
|
||||
{
|
||||
exception = "Your new plan does not allow the SCIM feature. " +
|
||||
"Disable your SCIM configuration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseCustomPermissions && UseCustomPermissions &&
|
||||
OrganizationUsers.Any(ou => ou.Type == OrganizationUserType.Custom))
|
||||
{
|
||||
exception = "Your new plan does not allow the Custom Permissions feature. " +
|
||||
"Disable your Custom Permissions configuration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!license.UseResetPassword && UseResetPassword &&
|
||||
Policies.Any(p => p.Type == PolicyType.ResetPassword && p.Enabled))
|
||||
{
|
||||
exception = "Your new license does not allow the Password Reset feature. "
|
||||
+ "Disable your Password Reset policy.";
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public Organization ToOrganization()
|
||||
{
|
||||
// Any new Organization properties must be added here for them to flow through to self-hosted organizations
|
||||
return new Organization
|
||||
{
|
||||
Id = Id,
|
||||
Identifier = Identifier,
|
||||
Name = Name,
|
||||
BusinessName = BusinessName,
|
||||
BusinessAddress1 = BusinessAddress1,
|
||||
BusinessAddress2 = BusinessAddress2,
|
||||
BusinessAddress3 = BusinessAddress3,
|
||||
BusinessCountry = BusinessCountry,
|
||||
BusinessTaxNumber = BusinessTaxNumber,
|
||||
BillingEmail = BillingEmail,
|
||||
Plan = Plan,
|
||||
PlanType = PlanType,
|
||||
Seats = Seats,
|
||||
MaxCollections = MaxCollections,
|
||||
UsePolicies = UsePolicies,
|
||||
UseSso = UseSso,
|
||||
UseKeyConnector = UseKeyConnector,
|
||||
UseScim = UseScim,
|
||||
UseGroups = UseGroups,
|
||||
UseDirectory = UseDirectory,
|
||||
UseEvents = UseEvents,
|
||||
UseTotp = UseTotp,
|
||||
Use2fa = Use2fa,
|
||||
UseApi = UseApi,
|
||||
UseResetPassword = UseResetPassword,
|
||||
UseSecretsManager = UseSecretsManager,
|
||||
SelfHost = SelfHost,
|
||||
UsersGetPremium = UsersGetPremium,
|
||||
UseCustomPermissions = UseCustomPermissions,
|
||||
Storage = Storage,
|
||||
MaxStorageGb = MaxStorageGb,
|
||||
Gateway = Gateway,
|
||||
GatewayCustomerId = GatewayCustomerId,
|
||||
GatewaySubscriptionId = GatewaySubscriptionId,
|
||||
ReferenceData = ReferenceData,
|
||||
Enabled = Enabled,
|
||||
LicenseKey = LicenseKey,
|
||||
PublicKey = PublicKey,
|
||||
PrivateKey = PrivateKey,
|
||||
TwoFactorProviders = TwoFactorProviders,
|
||||
ExpirationDate = ExpirationDate,
|
||||
CreationDate = CreationDate,
|
||||
RevisionDate = RevisionDate,
|
||||
MaxAutoscaleSeats = MaxAutoscaleSeats,
|
||||
OwnersNotifiedOfAutoscaling = OwnersNotifiedOfAutoscaling,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Mail;
|
||||
|
||||
public class OrganizationDomainUnverifiedViewModel
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
|
||||
public class CreateOrganizationDomainCommand : ICreateOrganizationDomainCommand
|
||||
{
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IDnsResolverService _dnsResolverService;
|
||||
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
|
||||
public CreateOrganizationDomainCommand(
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IEventService eventService,
|
||||
IDnsResolverService dnsResolverService,
|
||||
ILogger<VerifyOrganizationDomainCommand> logger,
|
||||
IGlobalSettings globalSettings)
|
||||
{
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
_eventService = eventService;
|
||||
_dnsResolverService = dnsResolverService;
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public async Task<OrganizationDomain> CreateAsync(OrganizationDomain organizationDomain)
|
||||
{
|
||||
//Domains claimed and verified by an organization cannot be claimed
|
||||
var claimedDomain =
|
||||
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(organizationDomain.DomainName);
|
||||
if (claimedDomain.Any())
|
||||
{
|
||||
throw new ConflictException("The domain is not available to be claimed.");
|
||||
}
|
||||
|
||||
//check for duplicate domain entry for an organization
|
||||
var duplicateOrgDomain =
|
||||
await _organizationDomainRepository.GetDomainByOrgIdAndDomainNameAsync(organizationDomain.OrganizationId,
|
||||
organizationDomain.DomainName);
|
||||
if (duplicateOrgDomain is not null)
|
||||
{
|
||||
throw new ConflictException("A domain already exists for this organization.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (await _dnsResolverService.ResolveAsync(organizationDomain.DomainName, organizationDomain.Txt))
|
||||
{
|
||||
organizationDomain.SetVerifiedDate();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error verifying Organization domain.", e);
|
||||
}
|
||||
|
||||
organizationDomain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
|
||||
organizationDomain.SetLastCheckedDate();
|
||||
|
||||
var orgDomain = await _organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
await _eventService.LogOrganizationDomainEventAsync(orgDomain, EventType.OrganizationDomain_Added);
|
||||
await _eventService.LogOrganizationDomainEventAsync(orgDomain,
|
||||
orgDomain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);
|
||||
|
||||
return orgDomain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
|
||||
public class DeleteOrganizationDomainCommand : IDeleteOrganizationDomainCommand
|
||||
{
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
private readonly IEventService _eventService;
|
||||
|
||||
public DeleteOrganizationDomainCommand(IOrganizationDomainRepository organizationDomainRepository,
|
||||
IEventService eventService)
|
||||
{
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var domain = await _organizationDomainRepository.GetByIdAsync(id);
|
||||
if (domain is null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _organizationDomainRepository.DeleteAsync(domain);
|
||||
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Removed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
|
||||
public class GetOrganizationDomainByIdQuery : IGetOrganizationDomainByIdQuery
|
||||
{
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
|
||||
public GetOrganizationDomainByIdQuery(IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
}
|
||||
|
||||
public async Task<OrganizationDomain> GetOrganizationDomainById(Guid id)
|
||||
=> await _organizationDomainRepository.GetByIdAsync(id);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
|
||||
public class GetOrganizationDomainByOrganizationIdQuery : IGetOrganizationDomainByOrganizationIdQuery
|
||||
{
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
|
||||
public GetOrganizationDomainByOrganizationIdQuery(IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
}
|
||||
|
||||
public async Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationId(Guid orgId)
|
||||
=> await _organizationDomainRepository.GetDomainsByOrganizationIdAsync(orgId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
|
||||
public interface ICreateOrganizationDomainCommand
|
||||
{
|
||||
Task<OrganizationDomain> CreateAsync(OrganizationDomain organizationDomain);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
|
||||
public interface IDeleteOrganizationDomainCommand
|
||||
{
|
||||
Task DeleteAsync(Guid id);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
|
||||
public interface IGetOrganizationDomainByIdQuery
|
||||
{
|
||||
Task<OrganizationDomain> GetOrganizationDomainById(Guid id);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
|
||||
public interface IGetOrganizationDomainByOrganizationIdQuery
|
||||
{
|
||||
Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationId(Guid orgId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
|
||||
public interface IVerifyOrganizationDomainCommand
|
||||
{
|
||||
Task<OrganizationDomain> VerifyOrganizationDomain(Guid id);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
|
||||
public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
|
||||
{
|
||||
private readonly IOrganizationDomainRepository _organizationDomainRepository;
|
||||
private readonly IDnsResolverService _dnsResolverService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
|
||||
|
||||
public VerifyOrganizationDomainCommand(
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IDnsResolverService dnsResolverService,
|
||||
IEventService eventService,
|
||||
ILogger<VerifyOrganizationDomainCommand> logger)
|
||||
{
|
||||
_organizationDomainRepository = organizationDomainRepository;
|
||||
_dnsResolverService = dnsResolverService;
|
||||
_eventService = eventService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationDomain> VerifyOrganizationDomain(Guid id)
|
||||
{
|
||||
var domain = await _organizationDomainRepository.GetByIdAsync(id);
|
||||
if (domain is null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (domain.VerifiedDate is not null)
|
||||
{
|
||||
domain.SetLastCheckedDate();
|
||||
await _organizationDomainRepository.ReplaceAsync(domain);
|
||||
throw new ConflictException("Domain has already been verified.");
|
||||
}
|
||||
|
||||
var claimedDomain =
|
||||
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName);
|
||||
if (claimedDomain.Any())
|
||||
{
|
||||
domain.SetLastCheckedDate();
|
||||
await _organizationDomainRepository.ReplaceAsync(domain);
|
||||
throw new ConflictException("The domain is not available to be claimed.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt))
|
||||
{
|
||||
domain.SetVerifiedDate();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error verifying Organization domain. {errorMessage}", e.Message);
|
||||
}
|
||||
|
||||
domain.SetLastCheckedDate();
|
||||
await _organizationDomainRepository.ReplaceAsync(domain);
|
||||
|
||||
await _eventService.LogOrganizationDomainEventAsync(domain,
|
||||
domain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer
|
||||
throw new BadRequestException("Invalid installation id");
|
||||
}
|
||||
|
||||
var subInfo = await _paymentService.GetSubscriptionAsync(organization);
|
||||
return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version);
|
||||
var subscriptionInfo = await _paymentService.GetSubscriptionAsync(organization);
|
||||
return new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationLicenseCommand
|
||||
{
|
||||
Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization,
|
||||
OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
|
||||
|
||||
public class UpdateOrganizationLicenseCommand : IUpdateOrganizationLicenseCommand
|
||||
{
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
|
||||
public UpdateOrganizationLicenseCommand(
|
||||
ILicensingService licensingService,
|
||||
IGlobalSettings globalSettings,
|
||||
IOrganizationService organizationService)
|
||||
{
|
||||
_licensingService = licensingService;
|
||||
_globalSettings = globalSettings;
|
||||
_organizationService = organizationService;
|
||||
}
|
||||
|
||||
public async Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization,
|
||||
OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey)
|
||||
{
|
||||
if (currentOrganizationUsingLicenseKey != null && currentOrganizationUsingLicenseKey.Id != selfHostedOrganization.Id)
|
||||
{
|
||||
throw new BadRequestException("License is already in use by another organization.");
|
||||
}
|
||||
|
||||
var canUse = license.CanUse(_globalSettings, _licensingService, out var exception) &&
|
||||
selfHostedOrganization.CanUseLicense(license, out exception);
|
||||
|
||||
if (!canUse)
|
||||
{
|
||||
throw new BadRequestException(exception);
|
||||
}
|
||||
|
||||
await WriteLicenseFileAsync(selfHostedOrganization, license);
|
||||
await UpdateOrganizationAsync(selfHostedOrganization, license);
|
||||
}
|
||||
|
||||
private async Task WriteLicenseFileAsync(Organization organization, OrganizationLicense license)
|
||||
{
|
||||
var dir = $"{_globalSettings.LicenseDirectory}/organization";
|
||||
Directory.CreateDirectory(dir);
|
||||
await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create);
|
||||
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
|
||||
}
|
||||
|
||||
private async Task UpdateOrganizationAsync(SelfHostedOrganizationDetails selfHostedOrganizationDetails, OrganizationLicense license)
|
||||
{
|
||||
var organization = selfHostedOrganizationDetails.ToOrganization();
|
||||
organization.UpdateFromLicense(license);
|
||||
|
||||
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ using Bit.Core.OrganizationFeatures.OrganizationCollections;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
|
||||
@@ -34,7 +36,8 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationApiKeyCommandsQueries();
|
||||
services.AddOrganizationCollectionCommands();
|
||||
services.AddOrganizationGroupCommands();
|
||||
services.AddOrganizationLicenseCommandQueries();
|
||||
services.AddOrganizationLicenseCommandsQueries();
|
||||
services.AddOrganizationDomainCommandsQueries();
|
||||
}
|
||||
|
||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||
@@ -88,10 +91,20 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddScoped<IUpdateGroupCommand, UpdateGroupCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationLicenseCommandQueries(this IServiceCollection services)
|
||||
private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICloudGetOrganizationLicenseQuery, CloudGetOrganizationLicenseQuery>();
|
||||
services.AddScoped<ISelfHostedGetOrganizationLicenseQuery, SelfHostedGetOrganizationLicenseQuery>();
|
||||
services.AddScoped<IUpdateOrganizationLicenseCommand, UpdateOrganizationLicenseCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICreateOrganizationDomainCommand, CreateOrganizationDomainCommand>();
|
||||
services.AddScoped<IVerifyOrganizationDomainCommand, VerifyOrganizationDomainCommand>();
|
||||
services.AddScoped<IGetOrganizationDomainByIdQuery, GetOrganizationDomainByIdQuery>();
|
||||
services.AddScoped<IGetOrganizationDomainByOrganizationIdQuery, GetOrganizationDomainByOrganizationIdQuery>();
|
||||
services.AddScoped<IDeleteOrganizationDomainCommand, DeleteOrganizationDomainCommand>();
|
||||
}
|
||||
|
||||
private static void AddTokenizers(this IServiceCollection services)
|
||||
|
||||
15
src/Core/Repositories/IOrganizationDomainRepository.cs
Normal file
15
src/Core/Repositories/IOrganizationDomainRepository.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
|
||||
namespace Bit.Core.Repositories;
|
||||
|
||||
public interface IOrganizationDomainRepository : IRepository<OrganizationDomain, Guid>
|
||||
{
|
||||
Task<ICollection<OrganizationDomain>> GetClaimedDomainsByDomainNameAsync(string domainName);
|
||||
Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationIdAsync(Guid orgId);
|
||||
Task<ICollection<OrganizationDomain>> GetManyByNextRunDateAsync(DateTime date);
|
||||
Task<OrganizationDomainSsoDetailsData> GetOrganizationDomainSsoDetailsAsync(string email);
|
||||
Task<OrganizationDomain> GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName);
|
||||
Task<ICollection<OrganizationDomain>> GetExpiredOrganizationDomainsAsync();
|
||||
Task<bool> DeleteExpiredAsync(int expirationPeriod);
|
||||
}
|
||||
@@ -11,4 +11,6 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||
Task<ICollection<Organization>> SearchAsync(string name, string userEmail, bool? paid, int skip, int take);
|
||||
Task UpdateStorageAsync(Guid id);
|
||||
Task<ICollection<OrganizationAbility>> GetManyAbilitiesAsync();
|
||||
Task<Organization> GetByLicenseKeyAsync(string licenseKey);
|
||||
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public interface IOrganizationUserRepository : IRepository<OrganizationUser, Gui
|
||||
Task<ICollection<OrganizationUser>> GetManyByUserAsync(Guid userId);
|
||||
Task<ICollection<OrganizationUser>> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type);
|
||||
Task<int> GetCountByOrganizationAsync(Guid organizationId, string email, bool onlyRegisteredUsers);
|
||||
Task<int> GetOccupiedSeatCountByOrganizationIdAsync(Guid organizationId);
|
||||
Task<ICollection<string>> SelectKnownEmailsAsync(Guid organizationId, IEnumerable<string> emails, bool onlyRegisteredUsers);
|
||||
Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, Guid userId);
|
||||
Task<Tuple<OrganizationUser, ICollection<CollectionAccessSelection>>> GetByIdWithCollectionsAsync(Guid id);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
|
||||
public interface ICreateAccessPoliciesCommand
|
||||
{
|
||||
Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies);
|
||||
Task<IEnumerable<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> accessPolicies, Guid userId, AccessClientType accessType);
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public interface IDeleteAccessPolicyCommand
|
||||
{
|
||||
Task DeleteAsync(Guid id);
|
||||
Task DeleteAsync(Guid id, Guid userId);
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
|
||||
public interface IUpdateAccessPolicyCommand
|
||||
{
|
||||
public Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write);
|
||||
public Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write, Guid userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.SecretsManager.Commands.Porting.Interfaces;
|
||||
|
||||
public interface IImportCommand
|
||||
{
|
||||
Task ImportAsync(Guid organizationId, SMImport import);
|
||||
SMImport AssignNewIds(SMImport import);
|
||||
}
|
||||
41
src/Core/SecretsManager/Commands/Porting/SMImport.cs
Normal file
41
src/Core/SecretsManager/Commands/Porting/SMImport.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Bit.Core.SecretsManager.Commands.Porting;
|
||||
|
||||
public class SMImport
|
||||
{
|
||||
public IEnumerable<InnerProject> Projects { get; set; }
|
||||
public IEnumerable<InnerSecret> Secrets { get; set; }
|
||||
|
||||
public class InnerProject
|
||||
{
|
||||
public InnerProject() { }
|
||||
|
||||
public InnerProject(Core.SecretsManager.Entities.Project project)
|
||||
{
|
||||
Id = project.Id;
|
||||
Name = project.Name;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class InnerSecret
|
||||
{
|
||||
public InnerSecret() { }
|
||||
|
||||
public InnerSecret(Core.SecretsManager.Entities.Secret secret)
|
||||
{
|
||||
Id = secret.Id;
|
||||
Key = secret.Key;
|
||||
Value = secret.Value;
|
||||
Note = secret.Note;
|
||||
ProjectIds = secret.Projects != null && secret.Projects.Any() ? secret.Projects.Select(p => p.Id) : null;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Note { get; set; }
|
||||
public IEnumerable<Guid> ProjectIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
||||
|
||||
public interface ICreateProjectCommand
|
||||
{
|
||||
Task<Project> CreateAsync(Project project);
|
||||
Task<Project> CreateAsync(Project project, Guid userId);
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface ICreateSecretCommand
|
||||
{
|
||||
Task<Secret> CreateAsync(Secret secret);
|
||||
Task<Secret> CreateAsync(Secret secret, Guid userId);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface IDeleteSecretCommand
|
||||
{
|
||||
Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids);
|
||||
Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids, Guid userId);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
|
||||
public interface IUpdateSecretCommand
|
||||
{
|
||||
Task<Secret> UpdateAsync(Secret secret);
|
||||
Task<Secret> UpdateAsync(Secret secret, Guid userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||
|
||||
public interface IRevokeAccessTokensCommand
|
||||
{
|
||||
Task RevokeAsync(ServiceAccount serviceAccount, IEnumerable<Guid> ids);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
||||
|
||||
public interface IEmptyTrashCommand
|
||||
{
|
||||
Task EmptyTrash(Guid organizationId, List<Guid> ids);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
||||
|
||||
public interface IRestoreTrashCommand
|
||||
{
|
||||
Task RestoreTrash(Guid organizationId, List<Guid> ids);
|
||||
}
|
||||
@@ -24,34 +24,39 @@ public abstract class BaseAccessPolicy
|
||||
public class UserProjectAccessPolicy : BaseAccessPolicy
|
||||
{
|
||||
public Guid? OrganizationUserId { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public User? User { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public Project? GrantedProject { get; set; }
|
||||
}
|
||||
|
||||
public class UserServiceAccountAccessPolicy : BaseAccessPolicy
|
||||
{
|
||||
public Guid? OrganizationUserId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public User? User { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public ServiceAccount? GrantedServiceAccount { get; set; }
|
||||
}
|
||||
|
||||
public class GroupProjectAccessPolicy : BaseAccessPolicy
|
||||
{
|
||||
public Guid? GroupId { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public Group? Group { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public Project? GrantedProject { get; set; }
|
||||
}
|
||||
|
||||
public class GroupServiceAccountAccessPolicy : BaseAccessPolicy
|
||||
{
|
||||
public Guid? GroupId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public Group? Group { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public ServiceAccount? GrantedServiceAccount { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceAccountProjectAccessPolicy : BaseAccessPolicy
|
||||
{
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public ServiceAccount? ServiceAccount { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public Project? GrantedProject { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Repositories;
|
||||
@@ -10,6 +11,8 @@ public interface IAccessPolicyRepository
|
||||
Task<BaseAccessPolicy?> GetByIdAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
|
||||
AccessClientType accessType);
|
||||
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
|
||||
Task DeleteAsync(Guid id);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user