mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[EC-261] SCIM (#2105)
* scim project stub * some scim models and v2 controllers * implement some v2 scim endpoints * fix spacing * api key auth * EC-261 - SCIM Org API Key and connection type config * EC-261 - Fix lint errors/formatting * updates for okta implementation testing * fix var ref * updates from testing with Okta * implement scim context via provider parsing * support single and list of ids for add/remove groups * log ops not handled * touch up scim context * group list filtering * EC-261 - Additional SCIM provider types * EC-265 - UseScim flag and license update * EC-265 - SCIM provider type of default (0) * EC-265 - Add Scim URL and update connection validation * EC-265 - Model validation and cleanup for SCIM keys * implement scim org connection * EC-265 - Ensure ServiceUrl is not persisted to DB * EC-265 - Exclude provider type from DB if not configured * EC-261 - EF Migrations for SCIM * add docker builds for scim * EC-261 - Fix failing permissions tests * EC-261 - Fix unit tests and pgsql migrations * Formatting fixes from linter * EC-265 - Remove service URL from scim config * EC-265 - Fix unit tests, removed wayward validation * EC-265 - Require self-hosted for billing sync org conn * EC-265 - Fix formatting issues - whitespace * EC-261 - PR feedback and cleanup * scim constants rename * no scim settings right now * update project name * delete package lock * update appsettings configs for scim * use default scim provider for context Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
This commit is contained in:
@ -344,6 +344,12 @@ namespace Bit.Core.Context
|
||||
&& (o.Permissions?.ManageSso ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ManageScim(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.ManageScim ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ManageUsers(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
@ -469,7 +475,8 @@ namespace Bit.Core.Context
|
||||
ManagePolicies = hasClaim("managepolicies"),
|
||||
ManageSso = hasClaim("managesso"),
|
||||
ManageUsers = hasClaim("manageusers"),
|
||||
ManageResetPassword = hasClaim("manageresetpassword")
|
||||
ManageResetPassword = hasClaim("manageresetpassword"),
|
||||
ManageScim = hasClaim("managescim"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,7 @@ namespace Bit.Core.Context
|
||||
Task<bool> ManagePolicies(Guid orgId);
|
||||
Task<bool> ManageSso(Guid orgId);
|
||||
Task<bool> ManageUsers(Guid orgId);
|
||||
Task<bool> ManageScim(Guid orgId);
|
||||
Task<bool> ManageResetPassword(Guid orgId);
|
||||
Task<bool> ManageBilling(Guid orgId);
|
||||
Task<bool> ProviderUserForOrgAsync(Guid orgId);
|
||||
|
@ -37,6 +37,7 @@ namespace Bit.Core.Entities
|
||||
public bool UsePolicies { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseKeyConnector { get; set; }
|
||||
public bool UseScim { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
|
@ -2,7 +2,8 @@
|
||||
{
|
||||
public enum OrganizationApiKeyType : byte
|
||||
{
|
||||
Default,
|
||||
BillingSync,
|
||||
Default = 0,
|
||||
BillingSync = 1,
|
||||
Scim = 2,
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,6 @@
|
||||
public enum OrganizationConnectionType : byte
|
||||
{
|
||||
CloudBillingSync = 1,
|
||||
Scim = 2,
|
||||
}
|
||||
}
|
||||
|
13
src/Core/Enums/ScimProviderType.cs
Normal file
13
src/Core/Enums/ScimProviderType.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum ScimProviderType : byte
|
||||
{
|
||||
Default = 0,
|
||||
AzureAd = 1,
|
||||
Okta = 2,
|
||||
OneLogin = 3,
|
||||
JumpCloud = 4,
|
||||
GoogleWorkspace = 5,
|
||||
Rippling = 6,
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ namespace Bit.Core.Models.Business
|
||||
UsePolicies = org.UsePolicies;
|
||||
UseSso = org.UseSso;
|
||||
UseKeyConnector = org.UseKeyConnector;
|
||||
UseScim = org.UseScim;
|
||||
UseGroups = org.UseGroups;
|
||||
UseEvents = org.UseEvents;
|
||||
UseDirectory = org.UseDirectory;
|
||||
@ -105,6 +106,7 @@ namespace Bit.Core.Models.Business
|
||||
public bool UsePolicies { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseKeyConnector { get; set; }
|
||||
public bool UseScim { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
@ -129,10 +131,10 @@ namespace Bit.Core.Models.Business
|
||||
/// <summary>
|
||||
/// Represents the current version of the license format. Should be updated whenever new fields are added.
|
||||
/// </summary>
|
||||
private const int CURRENT_LICENSE_FILE_VERSION = 8;
|
||||
private const int CURRENT_LICENSE_FILE_VERSION = 10;
|
||||
private bool ValidLicenseVersion
|
||||
{
|
||||
get => Version is >= 1 and <= 9;
|
||||
get => Version is >= 1 and <= 10;
|
||||
}
|
||||
|
||||
public byte[] GetDataBytes(bool forHash = false)
|
||||
@ -162,6 +164,8 @@ namespace Bit.Core.Models.Business
|
||||
(Version >= 8 || !p.Name.Equals(nameof(UseResetPassword))) &&
|
||||
// UseKeyConnector was added in Version 9
|
||||
(Version >= 9 || !p.Name.Equals(nameof(UseKeyConnector))) &&
|
||||
// UseScim was added in Version 10
|
||||
(Version >= 10 || !p.Name.Equals(nameof(UseScim))) &&
|
||||
(
|
||||
!forHash ||
|
||||
(
|
||||
@ -270,6 +274,11 @@ namespace Bit.Core.Models.Business
|
||||
valid = organization.UseKeyConnector == UseKeyConnector;
|
||||
}
|
||||
|
||||
if (valid && Version >= 10)
|
||||
{
|
||||
valid = organization.UseScim == UseScim;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
else
|
||||
|
@ -17,6 +17,7 @@ namespace Bit.Core.Models.Data.Organizations
|
||||
Enabled = organization.Enabled;
|
||||
UseSso = organization.UseSso;
|
||||
UseKeyConnector = organization.UseKeyConnector;
|
||||
UseScim = organization.UseScim;
|
||||
UseResetPassword = organization.UseResetPassword;
|
||||
}
|
||||
|
||||
@ -28,6 +29,7 @@ namespace Bit.Core.Models.Data.Organizations
|
||||
public bool Enabled { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseKeyConnector { get; set; }
|
||||
public bool UseScim { get; set; }
|
||||
public bool UseResetPassword { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
public bool UsePolicies { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseKeyConnector { get; set; }
|
||||
public bool UseScim { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
|
@ -21,6 +21,7 @@ namespace Bit.Core.Models.Data
|
||||
public bool ManageSso { get; set; }
|
||||
public bool ManageUsers { get; set; }
|
||||
public bool ManageResetPassword { get; set; }
|
||||
public bool ManageScim { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<(bool Permission, string ClaimName)> ClaimsMap => new()
|
||||
@ -38,6 +39,7 @@ namespace Bit.Core.Models.Data
|
||||
(ManageSso, "managesso"),
|
||||
(ManageUsers, "manageusers"),
|
||||
(ManageResetPassword, "manageresetpassword"),
|
||||
(ManageScim, "managescim"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace Bit.Core.Models.Data
|
||||
public bool UsePolicies { get; set; }
|
||||
public bool UseSso { get; set; }
|
||||
public bool UseKeyConnector { get; set; }
|
||||
public bool UseScim { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
public bool UseEvents { get; set; }
|
||||
|
12
src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs
Normal file
12
src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.OrganizationConnectionConfigs
|
||||
{
|
||||
public class ScimConfig
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public ScimProviderType? ScimProvider { get; set; }
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ namespace Bit.Core.Models.StaticStore
|
||||
public bool HasApi { get; set; }
|
||||
public bool HasSso { get; set; }
|
||||
public bool HasKeyConnector { get; set; }
|
||||
public bool HasScim { get; set; }
|
||||
public bool HasResetPassword { get; set; }
|
||||
public bool UsersGetPremium { get; set; }
|
||||
|
||||
|
@ -6,6 +6,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
@ -39,6 +40,7 @@ namespace Bit.Core.Services
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly ITaxRateRepository _taxRateRepository;
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ILogger<OrganizationService> _logger;
|
||||
|
||||
@ -66,6 +68,7 @@ namespace Bit.Core.Services
|
||||
IGlobalSettings globalSettings,
|
||||
ITaxRateRepository taxRateRepository,
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
ICurrentContext currentContext,
|
||||
ILogger<OrganizationService> logger)
|
||||
{
|
||||
@ -91,6 +94,7 @@ namespace Bit.Core.Services
|
||||
_globalSettings = globalSettings;
|
||||
_taxRateRepository = taxRateRepository;
|
||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
_currentContext = currentContext;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -266,6 +270,17 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!newPlan.HasScim && organization.UseScim)
|
||||
{
|
||||
var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
|
||||
OrganizationConnectionType.Scim);
|
||||
if (scimConnections != null && scimConnections.Any(c => c.GetConfig<ScimConfig>()?.Enabled == true))
|
||||
{
|
||||
throw new BadRequestException("Your new plan does not allow the SCIM feature. " +
|
||||
"Disable your SCIM configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check storage?
|
||||
|
||||
string paymentIntentClientSecret = null;
|
||||
@ -304,6 +319,7 @@ namespace Bit.Core.Services
|
||||
organization.UseApi = newPlan.HasApi;
|
||||
organization.UseSso = newPlan.HasSso;
|
||||
organization.UseKeyConnector = newPlan.HasKeyConnector;
|
||||
organization.UseScim = newPlan.HasScim;
|
||||
organization.UseResetPassword = newPlan.HasResetPassword;
|
||||
organization.SelfHost = newPlan.HasSelfHost;
|
||||
organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
|
||||
@ -702,6 +718,7 @@ namespace Bit.Core.Services
|
||||
UsePolicies = license.UsePolicies,
|
||||
UseSso = license.UseSso,
|
||||
UseKeyConnector = license.UseKeyConnector,
|
||||
UseScim = license.UseScim,
|
||||
UseGroups = license.UseGroups,
|
||||
UseDirectory = license.UseDirectory,
|
||||
UseEvents = license.UseEvents,
|
||||
@ -902,6 +919,17 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!license.UseScim && organization.UseScim)
|
||||
{
|
||||
var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
|
||||
OrganizationConnectionType.Scim);
|
||||
if (scimConnections != null && scimConnections.Any(c => c.GetConfig<ScimConfig>()?.Enabled == true))
|
||||
{
|
||||
throw new BadRequestException("Your new plan does not allow the SCIM feature. " +
|
||||
"Disable your SCIM configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!license.UseResetPassword && organization.UseResetPassword)
|
||||
{
|
||||
var resetPasswordPolicy =
|
||||
@ -933,6 +961,7 @@ namespace Bit.Core.Services
|
||||
organization.UsePolicies = license.UsePolicies;
|
||||
organization.UseSso = license.UseSso;
|
||||
organization.UseKeyConnector = license.UseKeyConnector;
|
||||
organization.UseScim = license.UseScim;
|
||||
organization.UseResetPassword = license.UseResetPassword;
|
||||
organization.SelfHost = license.SelfHost;
|
||||
organization.UsersGetPremium = license.UsersGetPremium;
|
||||
|
@ -117,12 +117,14 @@
|
||||
private string _admin;
|
||||
private string _notifications;
|
||||
private string _sso;
|
||||
private string _scim;
|
||||
private string _internalApi;
|
||||
private string _internalIdentity;
|
||||
private string _internalAdmin;
|
||||
private string _internalNotifications;
|
||||
private string _internalSso;
|
||||
private string _internalVault;
|
||||
private string _internalScim;
|
||||
|
||||
public BaseServiceUriSettings(GlobalSettings globalSettings)
|
||||
{
|
||||
@ -157,6 +159,11 @@
|
||||
get => _globalSettings.BuildExternalUri(_sso, "sso");
|
||||
set => _sso = value;
|
||||
}
|
||||
public string Scim
|
||||
{
|
||||
get => _globalSettings.BuildExternalUri(_scim, "scim");
|
||||
set => _scim = value;
|
||||
}
|
||||
|
||||
public string InternalNotifications
|
||||
{
|
||||
@ -188,6 +195,11 @@
|
||||
get => _globalSettings.BuildInternalUri(_internalSso, "sso");
|
||||
set => _internalSso = value;
|
||||
}
|
||||
public string InternalScim
|
||||
{
|
||||
get => _globalSettings.BuildInternalUri(_scim, "scim");
|
||||
set => _internalScim = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class SqlSettings
|
||||
|
@ -10,11 +10,13 @@ namespace Bit.Core.Settings
|
||||
public string Admin { get; set; }
|
||||
public string Notifications { get; set; }
|
||||
public string Sso { get; set; }
|
||||
public string Scim { get; set; }
|
||||
public string InternalNotifications { get; set; }
|
||||
public string InternalAdmin { get; set; }
|
||||
public string InternalIdentity { get; set; }
|
||||
public string InternalApi { get; set; }
|
||||
public string InternalVault { get; set; }
|
||||
public string InternalSso { get; set; }
|
||||
public string InternalScim { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -414,6 +414,7 @@ namespace Bit.Core.Utilities
|
||||
HasApi = true,
|
||||
HasSso = true,
|
||||
HasKeyConnector = true,
|
||||
HasScim = true,
|
||||
HasResetPassword = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
@ -453,6 +454,7 @@ namespace Bit.Core.Utilities
|
||||
HasSelfHost = true,
|
||||
HasSso = true,
|
||||
HasKeyConnector = true,
|
||||
HasScim = true,
|
||||
HasResetPassword = true,
|
||||
UsersGetPremium = true,
|
||||
|
||||
|
Reference in New Issue
Block a user