mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -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:
@ -32,6 +32,7 @@ namespace Bit.Admin.Models
|
||||
UsePolicies = org.UsePolicies;
|
||||
UseSso = org.UseSso;
|
||||
UseKeyConnector = org.UseKeyConnector;
|
||||
UseScim = org.UseScim;
|
||||
UseGroups = org.UseGroups;
|
||||
UseDirectory = org.UseDirectory;
|
||||
UseEvents = org.UseEvents;
|
||||
@ -94,6 +95,8 @@ namespace Bit.Admin.Models
|
||||
public bool UseApi { get; set; }
|
||||
[Display(Name = "Reset Password")]
|
||||
public bool UseResetPassword { get; set; }
|
||||
[Display(Name = "SCIM")]
|
||||
public bool UseScim { get; set; }
|
||||
[Display(Name = "Self Host")]
|
||||
public bool SelfHost { get; set; }
|
||||
[Display(Name = "Users Get Premium")]
|
||||
@ -126,6 +129,7 @@ namespace Bit.Admin.Models
|
||||
existingOrganization.UsePolicies = UsePolicies;
|
||||
existingOrganization.UseSso = UseSso;
|
||||
existingOrganization.UseKeyConnector = UseKeyConnector;
|
||||
existingOrganization.UseScim = UseScim;
|
||||
existingOrganization.UseGroups = UseGroups;
|
||||
existingOrganization.UseDirectory = UseDirectory;
|
||||
existingOrganization.UseEvents = UseEvents;
|
||||
|
@ -32,6 +32,7 @@
|
||||
document.getElementById('@(nameof(Model.UseApi))').checked = true;
|
||||
document.getElementById('@(nameof(Model.SelfHost))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseResetPassword))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseScim))').checked = false;
|
||||
// Licensing
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
@ -65,6 +66,7 @@
|
||||
document.getElementById('@(nameof(Model.UseApi))').checked = true;
|
||||
document.getElementById('@(nameof(Model.SelfHost))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseResetPassword))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseScim))').checked = true;
|
||||
// Licensing
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
@ -219,6 +221,10 @@
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector">
|
||||
<label class="form-check-label" asp-for="UseKeyConnector"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseScim">
|
||||
<label class="form-check-label" asp-for="UseScim"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseDirectory">
|
||||
<label class="form-check-label" asp-for="UseDirectory"></label>
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "http://localhost:33656",
|
||||
"internalApi": "http://localhost:4000",
|
||||
"internalVault": "https://localhost:8080",
|
||||
"internalSso": "http://localhost:51822"
|
||||
"internalSso": "http://localhost:51822",
|
||||
"internalScim": "http://localhost:44559"
|
||||
},
|
||||
"mail": {
|
||||
"smtp": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
},
|
||||
"braintree": {
|
||||
"production": true
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
},
|
||||
"braintree": {
|
||||
"production": false
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": null,
|
||||
"internalApi": null,
|
||||
"internalVault": null,
|
||||
"internalSso": null
|
||||
"internalSso": null,
|
||||
"internalScim": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,11 @@ using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
[SelfHosted(SelfHostedOnly = true)]
|
||||
[Authorize("Application")]
|
||||
[Route("organizations/connections")]
|
||||
public class OrganizationConnectionsController : Controller
|
||||
@ -57,10 +55,10 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
if (!await HasPermissionAsync(model?.OrganizationId))
|
||||
{
|
||||
throw new BadRequestException("Only the owner of an organization can create a connection.");
|
||||
throw new BadRequestException($"You do not have permission to create a connection of type {model.Type}.");
|
||||
}
|
||||
|
||||
if (await HasConnectionTypeAsync(model))
|
||||
if (await HasConnectionTypeAsync(model, null, model.Type))
|
||||
{
|
||||
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
|
||||
}
|
||||
@ -68,15 +66,9 @@ namespace Bit.Api.Controllers
|
||||
switch (model.Type)
|
||||
{
|
||||
case OrganizationConnectionType.CloudBillingSync:
|
||||
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
|
||||
var license = await _licensingService.ReadOrganizationLicenseAsync(model.OrganizationId);
|
||||
if (!_licensingService.VerifyLicense(license))
|
||||
{
|
||||
throw new BadRequestException("Cannot verify license file.");
|
||||
}
|
||||
typedModel.ParsedConfig.CloudOrganizationId = license.Id;
|
||||
var connection = await _createOrganizationConnectionCommand.CreateAsync(typedModel.ToData());
|
||||
return new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
|
||||
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(null, model, ValidateBillingSyncConfig);
|
||||
case OrganizationConnectionType.Scim:
|
||||
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(null, model);
|
||||
default:
|
||||
throw new BadRequestException($"Unknown Organization connection Type: {model.Type}");
|
||||
}
|
||||
@ -91,12 +83,12 @@ namespace Bit.Api.Controllers
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await HasPermissionAsync(model?.OrganizationId))
|
||||
if (!await HasPermissionAsync(model?.OrganizationId, model?.Type))
|
||||
{
|
||||
throw new BadRequestException("Only the owner of an organization can update a connection.");
|
||||
throw new BadRequestException("You do not have permission to update this connection.");
|
||||
}
|
||||
|
||||
if (await HasConnectionTypeAsync(model, organizationConnectionId))
|
||||
if (await HasConnectionTypeAsync(model, organizationConnectionId, model.Type))
|
||||
{
|
||||
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
|
||||
}
|
||||
@ -104,11 +96,9 @@ namespace Bit.Api.Controllers
|
||||
switch (model.Type)
|
||||
{
|
||||
case OrganizationConnectionType.CloudBillingSync:
|
||||
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
|
||||
// We don't allow overwriting or changing the CloudOrganizationId so save it from the existing connection
|
||||
typedModel.ParsedConfig.CloudOrganizationId = existingOrganizationConnection.GetConfig<BillingSyncConfig>().CloudOrganizationId;
|
||||
var connection = await _updateOrganizationConnectionCommand.UpdateAsync(typedModel.ToData(organizationConnectionId));
|
||||
return new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
|
||||
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(organizationConnectionId, model);
|
||||
case OrganizationConnectionType.Scim:
|
||||
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(organizationConnectionId, model);
|
||||
default:
|
||||
throw new BadRequestException($"Unkown Organization connection Type: {model.Type}");
|
||||
}
|
||||
@ -117,22 +107,27 @@ namespace Bit.Api.Controllers
|
||||
[HttpGet("{organizationId}/{type}")]
|
||||
public async Task<OrganizationConnectionResponseModel> GetConnection(Guid organizationId, OrganizationConnectionType type)
|
||||
{
|
||||
if (!await HasPermissionAsync(organizationId))
|
||||
if (!await HasPermissionAsync(organizationId, type))
|
||||
{
|
||||
throw new BadRequestException("Only the owner of an organization can retrieve a connection.");
|
||||
throw new BadRequestException($"You do not have permission to retrieve a connection of type {type}.");
|
||||
}
|
||||
|
||||
var connections = await GetConnectionsAsync(organizationId);
|
||||
var connections = await GetConnectionsAsync(organizationId, type);
|
||||
var connection = connections.FirstOrDefault(c => c.Type == type);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case OrganizationConnectionType.CloudBillingSync:
|
||||
if (!_globalSettings.SelfHosted)
|
||||
{
|
||||
throw new BadRequestException($"Cannot get a {type} connection outside of a self-hosted instance.");
|
||||
}
|
||||
return new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
|
||||
case OrganizationConnectionType.Scim:
|
||||
return new OrganizationConnectionResponseModel(connection, typeof(ScimConfig));
|
||||
default:
|
||||
throw new BadRequestException($"Unkown Organization connection Type: {type}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[HttpDelete("{organizationConnectionId}")]
|
||||
@ -146,25 +141,70 @@ namespace Bit.Api.Controllers
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!await HasPermissionAsync(connection.OrganizationId))
|
||||
if (!await HasPermissionAsync(connection.OrganizationId, connection.Type))
|
||||
{
|
||||
throw new BadRequestException("Only the owner of an organization can remove a connection.");
|
||||
throw new BadRequestException($"You do not have permission to remove this connection of type {connection.Type}.");
|
||||
}
|
||||
|
||||
await _deleteOrganizationConnectionCommand.DeleteAsync(connection);
|
||||
}
|
||||
|
||||
private async Task<ICollection<OrganizationConnection>> GetConnectionsAsync(Guid organizationId) =>
|
||||
await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organizationId, OrganizationConnectionType.CloudBillingSync);
|
||||
private async Task<ICollection<OrganizationConnection>> GetConnectionsAsync(Guid organizationId, OrganizationConnectionType type) =>
|
||||
await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organizationId, type);
|
||||
|
||||
private async Task<bool> HasConnectionTypeAsync(OrganizationConnectionRequestModel model, Guid? connectionId = null)
|
||||
private async Task<bool> HasConnectionTypeAsync(OrganizationConnectionRequestModel model, Guid? connectionId,
|
||||
OrganizationConnectionType type)
|
||||
{
|
||||
var existingConnections = await GetConnectionsAsync(model.OrganizationId);
|
||||
var existingConnections = await GetConnectionsAsync(model.OrganizationId, type);
|
||||
|
||||
return existingConnections.Any(c => c.Type == model.Type && (!connectionId.HasValue || c.Id != connectionId.Value));
|
||||
}
|
||||
|
||||
private async Task<bool> HasPermissionAsync(Guid? organizationId) =>
|
||||
organizationId.HasValue && await _currentContext.OrganizationOwner(organizationId.Value);
|
||||
private async Task<bool> HasPermissionAsync(Guid? organizationId, OrganizationConnectionType? type = null)
|
||||
{
|
||||
if (!organizationId.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return type switch
|
||||
{
|
||||
OrganizationConnectionType.Scim => await _currentContext.ManageScim(organizationId.Value),
|
||||
_ => await _currentContext.OrganizationOwner(organizationId.Value),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ValidateBillingSyncConfig(OrganizationConnectionRequestModel<BillingSyncConfig> typedModel)
|
||||
{
|
||||
if (!_globalSettings.SelfHosted)
|
||||
{
|
||||
throw new BadRequestException($"Cannot create a {typedModel.Type} connection outside of a self-hosted instance.");
|
||||
}
|
||||
var license = await _licensingService.ReadOrganizationLicenseAsync(typedModel.OrganizationId);
|
||||
if (!_licensingService.VerifyLicense(license))
|
||||
{
|
||||
throw new BadRequestException("Cannot verify license file.");
|
||||
}
|
||||
typedModel.ParsedConfig.CloudOrganizationId = license.Id;
|
||||
}
|
||||
|
||||
private async Task<OrganizationConnectionResponseModel> CreateOrUpdateOrganizationConnectionAsync<T>(
|
||||
Guid? organizationConnectionId,
|
||||
OrganizationConnectionRequestModel model,
|
||||
Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null)
|
||||
where T : new()
|
||||
{
|
||||
var typedModel = new OrganizationConnectionRequestModel<T>(model);
|
||||
if (validateAction != null)
|
||||
{
|
||||
await validateAction(typedModel);
|
||||
}
|
||||
|
||||
var data = typedModel.ToData(organizationConnectionId);
|
||||
var connection = organizationConnectionId.HasValue
|
||||
? await _updateOrganizationConnectionCommand.UpdateAsync(data)
|
||||
: await _createOrganizationConnectionCommand.CreateAsync(data);
|
||||
|
||||
return new OrganizationConnectionResponseModel(connection, typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,7 +493,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!await _currentContext.OrganizationOwner(orgIdGuid))
|
||||
if (!await HasApiKeyAccessAsync(orgIdGuid, model.Type))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -504,9 +504,9 @@ namespace Bit.Api.Controllers
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (model.Type == OrganizationApiKeyType.BillingSync)
|
||||
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim)
|
||||
{
|
||||
// Non-enterprise orgs should not be able to create or view an apikey of billing sync key type
|
||||
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
if (plan.Product != ProductType.Enterprise)
|
||||
{
|
||||
@ -523,7 +523,8 @@ namespace Bit.Api.Controllers
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (!await _userService.VerifySecretAsync(user, model.Secret))
|
||||
if (model.Type != OrganizationApiKeyType.Scim
|
||||
&& !await _userService.VerifySecretAsync(user, model.Secret))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
||||
@ -535,15 +536,15 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}/api-key-information")]
|
||||
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id)
|
||||
[HttpGet("{id}/api-key-information/{type?}")]
|
||||
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id, OrganizationApiKeyType? type)
|
||||
{
|
||||
if (!await _currentContext.OrganizationOwner(id))
|
||||
if (!await HasApiKeyAccessAsync(id, type))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var apiKeys = await _organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(id);
|
||||
var apiKeys = await _organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(id, type);
|
||||
|
||||
return new ListResponseModel<OrganizationApiKeyInformation>(
|
||||
apiKeys.Select(k => new OrganizationApiKeyInformation(k)));
|
||||
@ -553,7 +554,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!await _currentContext.OrganizationOwner(orgIdGuid))
|
||||
if (!await HasApiKeyAccessAsync(orgIdGuid, model.Type))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -573,7 +574,8 @@ namespace Bit.Api.Controllers
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (!await _userService.VerifySecretAsync(user, model.Secret))
|
||||
if (model.Type != OrganizationApiKeyType.Scim
|
||||
&& !await _userService.VerifySecretAsync(user, model.Secret))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
||||
@ -586,6 +588,15 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> HasApiKeyAccessAsync(Guid orgId, OrganizationApiKeyType? type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
OrganizationApiKeyType.Scim => await _currentContext.ManageScim(orgId),
|
||||
_ => await _currentContext.OrganizationOwner(orgId),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id}/tax")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<TaxInfoResponseModel> GetTaxInfo(string id)
|
||||
|
@ -35,6 +35,7 @@ namespace Bit.Api.Models.Response.Organizations
|
||||
UsePolicies = organization.UsePolicies;
|
||||
UseSso = organization.UseSso;
|
||||
UseKeyConnector = organization.UseKeyConnector;
|
||||
UseScim = organization.UseScim;
|
||||
UseGroups = organization.UseGroups;
|
||||
UseDirectory = organization.UseDirectory;
|
||||
UseEvents = organization.UseEvents;
|
||||
@ -66,6 +67,7 @@ namespace Bit.Api.Models.Response.Organizations
|
||||
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; }
|
||||
|
@ -17,6 +17,7 @@ namespace Bit.Api.Models.Response
|
||||
UsePolicies = organization.UsePolicies;
|
||||
UseSso = organization.UseSso;
|
||||
UseKeyConnector = organization.UseKeyConnector;
|
||||
UseScim = organization.UseScim;
|
||||
UseGroups = organization.UseGroups;
|
||||
UseDirectory = organization.UseDirectory;
|
||||
UseEvents = organization.UseEvents;
|
||||
@ -63,6 +64,7 @@ namespace Bit.Api.Models.Response
|
||||
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; }
|
||||
|
@ -13,6 +13,7 @@ namespace Bit.Api.Models.Response
|
||||
UsePolicies = organization.UsePolicies;
|
||||
UseSso = organization.UseSso;
|
||||
UseKeyConnector = organization.UseKeyConnector;
|
||||
UseScim = organization.UseScim;
|
||||
UseGroups = organization.UseGroups;
|
||||
UseDirectory = organization.UseDirectory;
|
||||
UseEvents = organization.UseEvents;
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "http://localhost:33656",
|
||||
"internalApi": "http://localhost:4000",
|
||||
"internalVault": "https://localhost:8080",
|
||||
"internalSso": "http://localhost:51822"
|
||||
"internalSso": "http://localhost:51822",
|
||||
"internalScim": "http://localhost:44559"
|
||||
},
|
||||
"mail": {
|
||||
"smtp": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
},
|
||||
"braintree": {
|
||||
"production": true
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
},
|
||||
"braintree": {
|
||||
"production": false
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": null,
|
||||
"internalApi": null,
|
||||
"internalVault": null,
|
||||
"internalSso": null
|
||||
"internalSso": null,
|
||||
"internalScim": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "http://localhost:33656",
|
||||
"internalApi": "http://localhost:4000",
|
||||
"internalVault": "https://localhost:8080",
|
||||
"internalSso": "http://localhost:51822"
|
||||
"internalSso": "http://localhost:51822",
|
||||
"internalScim": "http://localhost:44559"
|
||||
},
|
||||
"mail": {
|
||||
"smtp": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
},
|
||||
"braintree": {
|
||||
"production": true
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
},
|
||||
"braintree": {
|
||||
"production": false
|
||||
|
@ -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,
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "http://localhost:33656",
|
||||
"internalApi": "http://localhost:4000",
|
||||
"internalVault": "https://localhost:8080",
|
||||
"internalSso": "http://localhost:51822"
|
||||
"internalSso": "http://localhost:51822",
|
||||
"internalScim": "http://localhost:44559"
|
||||
},
|
||||
"events": {
|
||||
"connectionString": "UseDevelopmentStorage=true"
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": null,
|
||||
"internalApi": null,
|
||||
"internalVault": null,
|
||||
"internalSso": null
|
||||
"internalSso": null,
|
||||
"internalScim": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
},
|
||||
"braintree": {
|
||||
"production": true
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
},
|
||||
"braintree": {
|
||||
"production": false
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": null,
|
||||
"internalApi": null,
|
||||
"internalVault": null,
|
||||
"internalSso": null
|
||||
"internalSso": null,
|
||||
"internalScim": null
|
||||
},
|
||||
"captcha": {
|
||||
"maximumFailedLoginAttempts": 0
|
||||
|
@ -23,6 +23,8 @@ namespace Bit.Infrastructure.EntityFramework
|
||||
if (provider == SupportedDatabaseProviders.Postgres)
|
||||
{
|
||||
options.UseNpgsql(connectionString);
|
||||
// Handle NpgSql Legacy Support for `timestamp without timezone` issue
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
}
|
||||
else if (provider == SupportedDatabaseProviders.MySql)
|
||||
{
|
||||
|
@ -83,6 +83,8 @@ namespace Bit.Infrastructure.EntityFramework.Repositories
|
||||
Using2fa = e.Use2fa && e.TwoFactorProviders != null,
|
||||
UseSso = e.UseSso,
|
||||
UseKeyConnector = e.UseKeyConnector,
|
||||
UseResetPassword = e.UseResetPassword,
|
||||
UseScim = e.UseScim,
|
||||
}).ToListAsync();
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries
|
||||
UsePolicies = x.o.UsePolicies,
|
||||
UseSso = x.o.UseSso,
|
||||
UseKeyConnector = x.o.UseKeyConnector,
|
||||
UseScim = x.o.UseScim,
|
||||
UseGroups = x.o.UseGroups,
|
||||
UseDirectory = x.o.UseDirectory,
|
||||
UseEvents = x.o.UseEvents,
|
||||
|
@ -20,6 +20,7 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries
|
||||
UsePolicies = x.o.UsePolicies,
|
||||
UseSso = x.o.UseSso,
|
||||
UseKeyConnector = x.o.UseKeyConnector,
|
||||
UseScim = x.o.UseScim,
|
||||
UseGroups = x.o.UseGroups,
|
||||
UseDirectory = x.o.UseDirectory,
|
||||
UseEvents = x.o.UseEvents,
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "http://localhost:33656",
|
||||
"internalApi": "http://localhost:4000",
|
||||
"internalVault": "https://localhost:8080",
|
||||
"internalSso": "http://localhost:51822"
|
||||
"internalSso": "http://localhost:51822",
|
||||
"internalScim": "http://localhost:44559"
|
||||
},
|
||||
"notifications": {
|
||||
"connectionString": "UseDevelopmentStorage=true"
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.bitwarden.com",
|
||||
"internalApi": "https://api.bitwarden.com",
|
||||
"internalVault": "https://vault.bitwarden.com",
|
||||
"internalSso": "https://sso.bitwarden.com"
|
||||
"internalSso": "https://sso.bitwarden.com",
|
||||
"internalScim": "https://scim.bitwarden.com"
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"internalApi": "https://api.qa.bitwarden.pw",
|
||||
"internalVault": "https://vault.qa.bitwarden.pw",
|
||||
"internalSso": "https://sso.qa.bitwarden.pw"
|
||||
"internalSso": "https://sso.qa.bitwarden.pw",
|
||||
"internalScim": "https://scim.qa.bitwarden.pw"
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"internalIdentity": null,
|
||||
"internalApi": null,
|
||||
"internalVault": null,
|
||||
"internalSso": null
|
||||
"internalSso": null,
|
||||
"internalScim": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,8 @@
|
||||
@RevisionDate DATETIME2(7),
|
||||
@OwnersNotifiedOfAutoscaling DATETIME2(7),
|
||||
@MaxAutoscaleSeats INT,
|
||||
@UseKeyConnector BIT = 0
|
||||
@UseKeyConnector BIT = 0,
|
||||
@UseScim BIT = 0
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -88,7 +89,8 @@ BEGIN
|
||||
[RevisionDate],
|
||||
[OwnersNotifiedOfAutoscaling],
|
||||
[MaxAutoscaleSeats],
|
||||
[UseKeyConnector]
|
||||
[UseKeyConnector],
|
||||
[UseScim]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -133,6 +135,7 @@ BEGIN
|
||||
@RevisionDate,
|
||||
@OwnersNotifiedOfAutoscaling,
|
||||
@MaxAutoscaleSeats,
|
||||
@UseKeyConnector
|
||||
@UseKeyConnector,
|
||||
@UseScim
|
||||
)
|
||||
END
|
@ -16,6 +16,7 @@ BEGIN
|
||||
[UsersGetPremium],
|
||||
[UseSso],
|
||||
[UseKeyConnector],
|
||||
[UseScim],
|
||||
[UseResetPassword],
|
||||
[Enabled]
|
||||
FROM
|
||||
|
@ -40,7 +40,8 @@
|
||||
@RevisionDate DATETIME2(7),
|
||||
@OwnersNotifiedOfAutoscaling DATETIME2(7),
|
||||
@MaxAutoscaleSeats INT,
|
||||
@UseKeyConnector BIT = 0
|
||||
@UseKeyConnector BIT = 0,
|
||||
@UseScim BIT = 0
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -88,7 +89,8 @@ BEGIN
|
||||
[RevisionDate] = @RevisionDate,
|
||||
[OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling,
|
||||
[MaxAutoscaleSeats] = @MaxAutoscaleSeats,
|
||||
[UseKeyConnector] = @UseKeyConnector
|
||||
[UseKeyConnector] = @UseKeyConnector,
|
||||
[UseScim] = @UseScim
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
|
@ -41,6 +41,7 @@
|
||||
[OwnersNotifiedOfAutoscaling] DATETIME2(7) NULL,
|
||||
[MaxAutoscaleSeats] INT NULL,
|
||||
[UseKeyConnector] BIT NOT NULL,
|
||||
[UseScim] BIT NOT NULL CONSTRAINT [DF_Organization_UseScim] DEFAULT (0),
|
||||
CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||
);
|
||||
|
||||
|
@ -8,6 +8,7 @@ SELECT
|
||||
O.[UsePolicies],
|
||||
O.[UseSso],
|
||||
O.[UseKeyConnector],
|
||||
O.[UseScim],
|
||||
O.[UseGroups],
|
||||
O.[UseDirectory],
|
||||
O.[UseEvents],
|
||||
|
@ -8,6 +8,7 @@ SELECT
|
||||
O.[UsePolicies],
|
||||
O.[UseSso],
|
||||
O.[UseKeyConnector],
|
||||
O.[UseScim],
|
||||
O.[UseGroups],
|
||||
O.[UseDirectory],
|
||||
O.[UseEvents],
|
||||
|
Reference in New Issue
Block a user