1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 09:02:48 -05:00

Merge branch 'main' into ac/pm-21612/unified-mariadb-cannot-edit-an-unconfirmed-user

This commit is contained in:
Thomas Rittson
2025-05-16 13:52:56 +10:00
committed by GitHub
67 changed files with 10198 additions and 340 deletions

3
.github/CODEOWNERS vendored
View File

@ -90,6 +90,9 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev
.github/workflows/test-database.yml @bitwarden/team-platform-dev .github/workflows/test-database.yml @bitwarden/team-platform-dev
.github/workflows/test.yml @bitwarden/team-platform-dev .github/workflows/test.yml @bitwarden/team-platform-dev
**/*Platform* @bitwarden/team-platform-dev **/*Platform* @bitwarden/team-platform-dev
**/.dockerignore @bitwarden/team-platform-dev
**/Dockerfile @bitwarden/team-platform-dev
**/entrypoint.sh @bitwarden/team-platform-dev
# Multiple owners - DO NOT REMOVE (BRE) # Multiple owners - DO NOT REMOVE (BRE)
**/packages.lock.json **/packages.lock.json

View File

@ -2,7 +2,9 @@ name: Build on PR Target
on: on:
pull_request_target: pull_request_target:
types: [opened, synchronize] types: [opened, synchronize, reopened]
branches:
- "main"
defaults: defaults:
run: run:

View File

@ -7,8 +7,14 @@ on:
- "main" - "main"
- "rc" - "rc"
- "hotfix-rc" - "hotfix-rc"
pull_request:
types: [opened, synchronize, reopened]
branches-ignore:
- main
pull_request_target: pull_request_target:
types: [opened, synchronize] types: [opened, synchronize, reopened]
branches:
- "main"
jobs: jobs:
check-run: check-run:

View File

@ -15,7 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"css-loader": "7.1.2", "css-loader": "7.1.2",
"expose-loader": "5.0.0", "expose-loader": "5.0.1",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"sass": "1.85.0", "sass": "1.85.0",
"sass-loader": "16.0.4", "sass-loader": "16.0.4",
@ -1083,9 +1083,9 @@
} }
}, },
"node_modules/expose-loader": { "node_modules/expose-loader": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.1.tgz",
"integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==", "integrity": "sha512-5YPZuszN/eWND/B+xuq5nIpb/l5TV1HYmdO6SubYtHv+HenVw9/6bn33Mm5reY8DNid7AVtbARvyUD34edfCtg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -14,7 +14,7 @@
}, },
"devDependencies": { "devDependencies": {
"css-loader": "7.1.2", "css-loader": "7.1.2",
"expose-loader": "5.0.0", "expose-loader": "5.0.1",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"sass": "1.85.0", "sass": "1.85.0",
"sass-loader": "16.0.4", "sass-loader": "16.0.4",

View File

@ -462,6 +462,7 @@ public class OrganizationsController : Controller
organization.UsersGetPremium = model.UsersGetPremium; organization.UsersGetPremium = model.UsersGetPremium;
organization.UseSecretsManager = model.UseSecretsManager; organization.UseSecretsManager = model.UseSecretsManager;
organization.UseRiskInsights = model.UseRiskInsights; organization.UseRiskInsights = model.UseRiskInsights;
organization.UseOrganizationDomains = model.UseOrganizationDomains;
organization.UseAdminSponsoredFamilies = model.UseAdminSponsoredFamilies; organization.UseAdminSponsoredFamilies = model.UseAdminSponsoredFamilies;
//secrets //secrets

View File

@ -102,7 +102,7 @@ public class OrganizationEditModel : OrganizationViewModel
MaxAutoscaleSmSeats = org.MaxAutoscaleSmSeats; MaxAutoscaleSmSeats = org.MaxAutoscaleSmSeats;
SmServiceAccounts = org.SmServiceAccounts; SmServiceAccounts = org.SmServiceAccounts;
MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts; MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts;
UseOrganizationDomains = org.UseOrganizationDomains;
_plans = plans; _plans = plans;
} }
@ -186,6 +186,8 @@ public class OrganizationEditModel : OrganizationViewModel
public int? SmServiceAccounts { get; set; } public int? SmServiceAccounts { get; set; }
[Display(Name = "Max Autoscale Machine Accounts")] [Display(Name = "Max Autoscale Machine Accounts")]
public int? MaxAutoscaleSmServiceAccounts { get; set; } public int? MaxAutoscaleSmServiceAccounts { get; set; }
[Display(Name = "Use Organization Domains")]
public bool UseOrganizationDomains { get; set; }
/** /**
* Creates a Plan[] object for use in Javascript * Creates a Plan[] object for use in Javascript
@ -215,6 +217,7 @@ public class OrganizationEditModel : OrganizationViewModel
Has2fa = p.Has2fa, Has2fa = p.Has2fa,
HasApi = p.HasApi, HasApi = p.HasApi,
HasSso = p.HasSso, HasSso = p.HasSso,
HasOrganizationDomains = p.HasOrganizationDomains,
HasKeyConnector = p.HasKeyConnector, HasKeyConnector = p.HasKeyConnector,
HasScim = p.HasScim, HasScim = p.HasScim,
HasResetPassword = p.HasResetPassword, HasResetPassword = p.HasResetPassword,
@ -315,6 +318,7 @@ public class OrganizationEditModel : OrganizationViewModel
existingOrganization.MaxAutoscaleSmSeats = MaxAutoscaleSmSeats; existingOrganization.MaxAutoscaleSmSeats = MaxAutoscaleSmSeats;
existingOrganization.SmServiceAccounts = SmServiceAccounts; existingOrganization.SmServiceAccounts = SmServiceAccounts;
existingOrganization.MaxAutoscaleSmServiceAccounts = MaxAutoscaleSmServiceAccounts; existingOrganization.MaxAutoscaleSmServiceAccounts = MaxAutoscaleSmServiceAccounts;
existingOrganization.UseOrganizationDomains = UseOrganizationDomains;
return existingOrganization; return existingOrganization;
} }
} }

View File

@ -124,6 +124,10 @@
<input type="checkbox" class="form-check-input" asp-for="UseSso" disabled='@(canEditPlan ? null : "disabled")'> <input type="checkbox" class="form-check-input" asp-for="UseSso" disabled='@(canEditPlan ? null : "disabled")'>
<label class="form-check-label" asp-for="UseSso"></label> <label class="form-check-label" asp-for="UseSso"></label>
</div> </div>
<div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="UseOrganizationDomains" disabled='@(canEditPlan ? null : "disabled")'>
<label class="form-check-label" asp-for="UseOrganizationDomains"></label>
</div>
<div class="form-check"> <div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector" disabled='@(canEditPlan ? null : "disabled")'> <input type="checkbox" class="form-check-input" asp-for="UseKeyConnector" disabled='@(canEditPlan ? null : "disabled")'>
<label class="form-check-label" asp-for="UseKeyConnector"></label> <label class="form-check-label" asp-for="UseKeyConnector"></label>

View File

@ -69,6 +69,7 @@
document.getElementById('@(nameof(Model.UseGroups))').checked = plan.hasGroups; document.getElementById('@(nameof(Model.UseGroups))').checked = plan.hasGroups;
document.getElementById('@(nameof(Model.UsePolicies))').checked = plan.hasPolicies; document.getElementById('@(nameof(Model.UsePolicies))').checked = plan.hasPolicies;
document.getElementById('@(nameof(Model.UseSso))').checked = plan.hasSso; document.getElementById('@(nameof(Model.UseSso))').checked = plan.hasSso;
document.getElementById('@(nameof(Model.UseOrganizationDomains))').checked = hasOrganizationDomains;
document.getElementById('@(nameof(Model.UseScim))').checked = plan.hasScim; document.getElementById('@(nameof(Model.UseScim))').checked = plan.hasScim;
document.getElementById('@(nameof(Model.UseDirectory))').checked = plan.hasDirectory; document.getElementById('@(nameof(Model.UseDirectory))').checked = plan.hasDirectory;
document.getElementById('@(nameof(Model.UseEvents))').checked = plan.hasEvents; document.getElementById('@(nameof(Model.UseEvents))').checked = plan.hasEvents;

View File

@ -17,7 +17,7 @@ public class ChargeBraintreeModel : IValidatableObject
{ {
if (Id != null) if (Id != null)
{ {
if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u') || if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u' && Id[0] != 'p') ||
!Guid.TryParse(Id.Substring(1, 32), out var guid)) !Guid.TryParse(Id.Substring(1, 32), out var guid))
{ {
yield return new ValidationResult("Customer Id is not a valid format."); yield return new ValidationResult("Customer Id is not a valid format.");

View File

@ -16,7 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"css-loader": "7.1.2", "css-loader": "7.1.2",
"expose-loader": "5.0.0", "expose-loader": "5.0.1",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"sass": "1.85.0", "sass": "1.85.0",
"sass-loader": "16.0.4", "sass-loader": "16.0.4",
@ -1084,9 +1084,9 @@
} }
}, },
"node_modules/expose-loader": { "node_modules/expose-loader": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.1.tgz",
"integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==", "integrity": "sha512-5YPZuszN/eWND/B+xuq5nIpb/l5TV1HYmdO6SubYtHv+HenVw9/6bn33Mm5reY8DNid7AVtbARvyUD34edfCtg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -15,7 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"css-loader": "7.1.2", "css-loader": "7.1.2",
"expose-loader": "5.0.0", "expose-loader": "5.0.1",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"sass": "1.85.0", "sass": "1.85.0",
"sass-loader": "16.0.4", "sass-loader": "16.0.4",

View File

@ -64,6 +64,7 @@ public class OrganizationResponseModel : ResponseModel
LimitItemDeletion = organization.LimitItemDeletion; LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights; UseRiskInsights = organization.UseRiskInsights;
UseOrganizationDomains = organization.UseOrganizationDomains;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
} }
@ -111,6 +112,7 @@ public class OrganizationResponseModel : ResponseModel
public bool LimitItemDeletion { get; set; } public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
} }

View File

@ -73,6 +73,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UserIsClaimedByOrganization = organizationIdsClaimingUser.Contains(organization.OrganizationId); UserIsClaimedByOrganization = organizationIdsClaimingUser.Contains(organization.OrganizationId);
UseRiskInsights = organization.UseRiskInsights; UseRiskInsights = organization.UseRiskInsights;
UseOrganizationDomains = organization.UseOrganizationDomains;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
if (organization.SsoConfig != null) if (organization.SsoConfig != null)
@ -153,6 +154,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
/// </remarks> /// </remarks>
public bool UserIsClaimedByOrganization { get; set; } public bool UserIsClaimedByOrganization { get; set; }
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
public bool IsAdminInitiated { get; set; } public bool IsAdminInitiated { get; set; }
} }

View File

@ -50,6 +50,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
LimitItemDeletion = organization.LimitItemDeletion; LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights; UseRiskInsights = organization.UseRiskInsights;
UseOrganizationDomains = organization.UseOrganizationDomains;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
} }
} }

View File

@ -32,6 +32,7 @@ public class PlanResponseModel : ResponseModel
HasTotp = plan.HasTotp; HasTotp = plan.HasTotp;
Has2fa = plan.Has2fa; Has2fa = plan.Has2fa;
HasSso = plan.HasSso; HasSso = plan.HasSso;
HasOrganizationDomains = plan.HasOrganizationDomains;
HasResetPassword = plan.HasResetPassword; HasResetPassword = plan.HasResetPassword;
UsersGetPremium = plan.UsersGetPremium; UsersGetPremium = plan.UsersGetPremium;
UpgradeSortOrder = plan.UpgradeSortOrder; UpgradeSortOrder = plan.UpgradeSortOrder;
@ -71,6 +72,7 @@ public class PlanResponseModel : ResponseModel
public bool Has2fa { get; set; } public bool Has2fa { get; set; }
public bool HasApi { get; set; } public bool HasApi { get; set; }
public bool HasSso { get; set; } public bool HasSso { get; set; }
public bool HasOrganizationDomains { get; set; }
public bool HasResetPassword { get; set; } public bool HasResetPassword { get; set; }
public bool UsersGetPremium { get; set; } public bool UsersGetPremium { get; set; }

View File

@ -315,28 +315,12 @@ public class CiphersController : Controller
{ {
var org = _currentContext.GetOrganization(organizationId); var org = _currentContext.GetOrganization(organizationId);
// If we're not an "admin", we don't need to check the ciphers // If we're not an "admin" or if we're not a provider user we don't need to check the ciphers
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true })) if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// Are we a provider user? If so, we need to be sure we're not restricted
// Once the feature flag is removed, this check can be combined with the above
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// Provider is restricted from editing ciphers, so we're not an "admin"
if (_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess))
{ {
return false; return false;
} }
// Provider is unrestricted, so we're an "admin", don't return early
}
else
{
// Not a provider or admin
return false;
}
}
// We know we're an "admin", now check the ciphers explicitly (in case admins are restricted) // We know we're an "admin", now check the ciphers explicitly (in case admins are restricted)
return await CanEditCiphersAsync(organizationId, cipherIds); return await CanEditCiphersAsync(organizationId, cipherIds);
} }
@ -350,28 +334,12 @@ public class CiphersController : Controller
var org = _currentContext.GetOrganization(organizationId); var org = _currentContext.GetOrganization(organizationId);
// If we're not an "admin", we don't need to check the ciphers // If we're not an "admin" or if we're a provider user we don't need to check the ciphers
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true })) if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// Are we a provider user? If so, we need to be sure we're not restricted
// Once the feature flag is removed, this check can be combined with the above
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// Provider is restricted from editing ciphers, so we're not an "admin"
if (_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess))
{ {
return false; return false;
} }
// Provider is unrestricted, so we're an "admin", don't return early
}
else
{
// Not a provider or admin
return false;
}
}
// If the user can edit all ciphers for the organization, just check they all belong to the org // If the user can edit all ciphers for the organization, just check they all belong to the org
if (await CanEditAllCiphersAsync(organizationId)) if (await CanEditAllCiphersAsync(organizationId))
{ {
@ -462,10 +430,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can edit all ciphers if RestrictProviderAccess is disabled // Provider users cannot edit ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;
@ -485,10 +453,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can only access organization ciphers if RestrictProviderAccess is disabled // Provider users cannot access organization ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;
@ -508,10 +476,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can only access all ciphers if RestrictProviderAccess is disabled // Provider users cannot access ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;

View File

@ -114,6 +114,11 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
/// </summary> /// </summary>
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
/// <summary>
/// If true, the organization can claim domains, which unlocks additional enterprise features
/// </summary>
public bool UseOrganizationDomains { get; set; }
/// <summary> /// <summary>
/// If set to true, admins can initiate organization-issued sponsorships. /// If set to true, admins can initiate organization-issued sponsorships.
/// </summary> /// </summary>
@ -319,5 +324,6 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
SmSeats = license.SmSeats; SmSeats = license.SmSeats;
SmServiceAccounts = license.SmServiceAccounts; SmServiceAccounts = license.SmServiceAccounts;
UseRiskInsights = license.UseRiskInsights; UseRiskInsights = license.UseRiskInsights;
UseOrganizationDomains = license.UseOrganizationDomains;
} }
} }

View File

@ -26,6 +26,7 @@ public class OrganizationAbility
LimitItemDeletion = organization.LimitItemDeletion; LimitItemDeletion = organization.LimitItemDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UseRiskInsights = organization.UseRiskInsights; UseRiskInsights = organization.UseRiskInsights;
UseOrganizationDomains = organization.UseOrganizationDomains;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
} }
@ -46,5 +47,6 @@ public class OrganizationAbility
public bool LimitItemDeletion { get; set; } public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
} }

View File

@ -59,6 +59,7 @@ public class OrganizationUserOrganizationDetails
public bool LimitItemDeletion { get; set; } public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
public bool? IsAdminInitiated { get; set; } public bool? IsAdminInitiated { get; set; }
} }

View File

@ -45,6 +45,7 @@ public class ProviderUserOrganizationDetails
public bool LimitItemDeletion { get; set; } public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; }
public bool UseRiskInsights { get; set; } public bool UseRiskInsights { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
public ProviderType ProviderType { get; set; } public ProviderType ProviderType { get; set; }
} }

View File

@ -104,7 +104,8 @@ public class CloudOrganizationSignUpCommand(
RevisionDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created, Status = OrganizationStatusType.Created,
UsePasswordManager = true, UsePasswordManager = true,
UseSecretsManager = signup.UseSecretsManager UseSecretsManager = signup.UseSecretsManager,
UseOrganizationDomains = plan.HasOrganizationDomains,
}; };
if (signup.UseSecretsManager) if (signup.UseSecretsManager)

View File

@ -449,6 +449,7 @@ public class OrganizationService : IOrganizationService
MaxStorageGb = 1, MaxStorageGb = 1,
UsePolicies = plan.HasPolicies, UsePolicies = plan.HasPolicies,
UseSso = plan.HasSso, UseSso = plan.HasSso,
UseOrganizationDomains = plan.HasOrganizationDomains,
UseGroups = plan.HasGroups, UseGroups = plan.HasGroups,
UseEvents = plan.HasEvents, UseEvents = plan.HasEvents,
UseDirectory = plan.HasDirectory, UseDirectory = plan.HasDirectory,
@ -570,6 +571,7 @@ public class OrganizationService : IOrganizationService
SmSeats = license.SmSeats, SmSeats = license.SmSeats,
SmServiceAccounts = license.SmServiceAccounts, SmServiceAccounts = license.SmServiceAccounts,
UseRiskInsights = license.UseRiskInsights, UseRiskInsights = license.UseRiskInsights,
UseOrganizationDomains = license.UseOrganizationDomains,
}; };
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false); var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);

View File

@ -108,6 +108,7 @@ public class RegisterUserCommand : IRegisterUserCommand
var result = await _userService.CreateUserAsync(user, masterPasswordHash); var result = await _userService.CreateUserAsync(user, masterPasswordHash);
if (result == IdentityResult.Success) if (result == IdentityResult.Success)
{ {
var sentWelcomeEmail = false;
if (!string.IsNullOrEmpty(user.ReferenceData)) if (!string.IsNullOrEmpty(user.ReferenceData))
{ {
var referenceData = JsonConvert.DeserializeObject<Dictionary<string, object>>(user.ReferenceData); var referenceData = JsonConvert.DeserializeObject<Dictionary<string, object>>(user.ReferenceData);
@ -115,6 +116,7 @@ public class RegisterUserCommand : IRegisterUserCommand
{ {
var initiationPath = value.ToString(); var initiationPath = value.ToString();
await SendAppropriateWelcomeEmailAsync(user, initiationPath); await SendAppropriateWelcomeEmailAsync(user, initiationPath);
sentWelcomeEmail = true;
if (!string.IsNullOrEmpty(initiationPath)) if (!string.IsNullOrEmpty(initiationPath))
{ {
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
@ -128,6 +130,11 @@ public class RegisterUserCommand : IRegisterUserCommand
} }
} }
if (!sentWelcomeEmail)
{
await _mailService.SendWelcomeEmailAsync(user);
}
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext)); await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
} }

View File

@ -42,6 +42,7 @@ public static class OrganizationLicenseConstants
public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod); public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod);
public const string Trial = nameof(Trial); public const string Trial = nameof(Trial);
public const string UseAdminSponsoredFamilies = nameof(UseAdminSponsoredFamilies); public const string UseAdminSponsoredFamilies = nameof(UseAdminSponsoredFamilies);
public const string UseOrganizationDomains = nameof(UseOrganizationDomains);
} }
public static class UserLicenseConstants public static class UserLicenseConstants

View File

@ -54,6 +54,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
new(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod), expirationWithoutGracePeriod.ToString(CultureInfo.InvariantCulture)), new(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod), expirationWithoutGracePeriod.ToString(CultureInfo.InvariantCulture)),
new(nameof(OrganizationLicenseConstants.Trial), trial.ToString()), new(nameof(OrganizationLicenseConstants.Trial), trial.ToString()),
new(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()), new(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()),
new(nameof(OrganizationLicenseConstants.UseOrganizationDomains), entity.UseOrganizationDomains.ToString()),
}; };
if (entity.Name is not null) if (entity.Name is not null)

View File

@ -24,6 +24,7 @@ public abstract record Plan
public bool Has2fa { get; protected init; } public bool Has2fa { get; protected init; }
public bool HasApi { get; protected init; } public bool HasApi { get; protected init; }
public bool HasSso { get; protected init; } public bool HasSso { get; protected init; }
public bool HasOrganizationDomains { get; protected init; }
public bool HasKeyConnector { get; protected init; } public bool HasKeyConnector { get; protected init; }
public bool HasScim { get; protected init; } public bool HasScim { get; protected init; }
public bool HasResetPassword { get; protected init; } public bool HasResetPassword { get; protected init; }

View File

@ -169,8 +169,6 @@ public static class FeatureFlagKeys
public const string NativeCreateAccountFlow = "native-create-account-flow"; public const string NativeCreateAccountFlow = "native-create-account-flow";
public const string AndroidImportLoginsFlow = "import-logins-flow"; public const string AndroidImportLoginsFlow = "import-logins-flow";
public const string AppReviewPrompt = "app-review-prompt"; public const string AppReviewPrompt = "app-review-prompt";
public const string EnablePasswordManagerSyncAndroid = "enable-password-manager-sync-android";
public const string EnablePasswordManagerSynciOS = "enable-password-manager-sync-ios";
public const string AndroidMutualTls = "mutual-tls"; public const string AndroidMutualTls = "mutual-tls";
public const string SingleTapPasskeyCreation = "single-tap-passkey-creation"; public const string SingleTapPasskeyCreation = "single-tap-passkey-creation";
public const string SingleTapPasskeyAuthentication = "single-tap-passkey-authentication"; public const string SingleTapPasskeyAuthentication = "single-tap-passkey-authentication";
@ -195,7 +193,6 @@ public static class FeatureFlagKeys
/* Vault Team */ /* Vault Team */
public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge"; public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge";
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form"; public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
public const string RestrictProviderAccess = "restrict-provider-access";
public const string SecurityTasks = "security-tasks"; public const string SecurityTasks = "security-tasks";
public const string CipherKeyEncryption = "cipher-key-encryption"; public const string CipherKeyEncryption = "cipher-key-encryption";
public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms"; public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms";

View File

@ -34,7 +34,7 @@
<PackageReference Include="DnsClient" Version="1.8.0" /> <PackageReference Include="DnsClient" Version="1.8.0" />
<PackageReference Include="Fido2.AspNet" Version="3.0.1" /> <PackageReference Include="Fido2.AspNet" Version="3.0.1" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" /> <PackageReference Include="Handlebars.Net" Version="2.1.6" />
<PackageReference Include="MailKit" Version="4.11.0" /> <PackageReference Include="MailKit" Version="4.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.49.0" /> <PackageReference Include="Microsoft.Azure.Cosmos" Version="3.49.0" />
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="4.2.0" /> <PackageReference Include="Microsoft.Azure.NotificationHubs" Version="4.2.0" />
@ -60,10 +60,10 @@
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" /> <PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" /> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.7.0" /> <PackageReference Include="LaunchDarkly.ServerSdk" Version="8.7.0" />
<PackageReference Include="Quartz" Version="3.13.1" /> <PackageReference Include="Quartz" Version="3.14.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" /> <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.13.1" /> <PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.14.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.0.0" /> <PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Pinned transitive dependencies"> <ItemGroup Label="Pinned transitive dependencies">

View File

@ -182,6 +182,7 @@ public class OrganizationLicense : ILicense
public bool Trial { get; set; } public bool Trial { get; set; }
public LicenseType? LicenseType { get; set; } public LicenseType? LicenseType { get; set; }
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; } public bool UseAdminSponsoredFamilies { get; set; }
public string Hash { get; set; } public string Hash { get; set; }
public string Signature { get; set; } public string Signature { get; set; }
@ -445,6 +446,7 @@ public class OrganizationLicense : ILicense
var smSeats = claimsPrincipal.GetValue<int?>(nameof(SmSeats)); var smSeats = claimsPrincipal.GetValue<int?>(nameof(SmSeats));
var smServiceAccounts = claimsPrincipal.GetValue<int?>(nameof(SmServiceAccounts)); var smServiceAccounts = claimsPrincipal.GetValue<int?>(nameof(SmServiceAccounts));
var useAdminSponsoredFamilies = claimsPrincipal.GetValue<bool>(nameof(UseAdminSponsoredFamilies)); var useAdminSponsoredFamilies = claimsPrincipal.GetValue<bool>(nameof(UseAdminSponsoredFamilies));
var useOrganizationDomains = claimsPrincipal.GetValue<bool>(nameof(UseOrganizationDomains));
return issued <= DateTime.UtcNow && return issued <= DateTime.UtcNow &&
expires >= DateTime.UtcNow && expires >= DateTime.UtcNow &&
@ -473,7 +475,8 @@ public class OrganizationLicense : ILicense
usePasswordManager == organization.UsePasswordManager && usePasswordManager == organization.UsePasswordManager &&
smSeats == organization.SmSeats && smSeats == organization.SmSeats &&
smServiceAccounts == organization.SmServiceAccounts && smServiceAccounts == organization.SmServiceAccounts &&
useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies; useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies &&
useOrganizationDomains == organization.UseOrganizationDomains;
} }

View File

@ -263,6 +263,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
organization.Use2fa = newPlan.Has2fa; organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPlan.HasApi; organization.UseApi = newPlan.HasApi;
organization.UseSso = newPlan.HasSso; organization.UseSso = newPlan.HasSso;
organization.UseOrganizationDomains = newPlan.HasOrganizationDomains;
organization.UseKeyConnector = newPlan.HasKeyConnector; organization.UseKeyConnector = newPlan.HasKeyConnector;
organization.UseScim = newPlan.HasScim; organization.UseScim = newPlan.HasScim;
organization.UseResetPassword = newPlan.HasResetPassword; organization.UseResetPassword = newPlan.HasResetPassword;

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema

View File

@ -107,6 +107,7 @@ public class OrganizationRepository : Repository<Core.AdminConsole.Entities.Orga
LimitItemDeletion = e.LimitItemDeletion, LimitItemDeletion = e.LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = e.AllowAdminAccessToAllCollectionItems, AllowAdminAccessToAllCollectionItems = e.AllowAdminAccessToAllCollectionItems,
UseRiskInsights = e.UseRiskInsights, UseRiskInsights = e.UseRiskInsights,
UseOrganizationDomains = e.UseOrganizationDomains,
UseAdminSponsoredFamilies = e.UseAdminSponsoredFamilies UseAdminSponsoredFamilies = e.UseAdminSponsoredFamilies
}).ToListAsync(); }).ToListAsync();
} }

View File

@ -71,7 +71,8 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
UseRiskInsights = o.UseRiskInsights, UseRiskInsights = o.UseRiskInsights,
UseAdminSponsoredFamilies = o.UseAdminSponsoredFamilies, UseAdminSponsoredFamilies = o.UseAdminSponsoredFamilies,
LimitItemDeletion = o.LimitItemDeletion, LimitItemDeletion = o.LimitItemDeletion,
IsAdminInitiated = os.IsAdminInitiated IsAdminInitiated = os.IsAdminInitiated,
UseOrganizationDomains = o.UseOrganizationDomains
}; };
return query; return query;
} }

View File

@ -49,8 +49,9 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan
LimitItemDeletion = x.o.LimitItemDeletion, LimitItemDeletion = x.o.LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems, AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems,
UseRiskInsights = x.o.UseRiskInsights, UseRiskInsights = x.o.UseRiskInsights,
ProviderType = x.p.Type,
UseOrganizationDomains = x.o.UseOrganizationDomains,
UseAdminSponsoredFamilies = x.o.UseAdminSponsoredFamilies, UseAdminSponsoredFamilies = x.o.UseAdminSponsoredFamilies,
ProviderType = x.p.Type
}); });
} }
} }

View File

@ -56,6 +56,7 @@ CREATE PROCEDURE [dbo].[Organization_Create]
@AllowAdminAccessToAllCollectionItems BIT = 0, @AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0, @UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0, @LimitItemDeletion BIT = 0,
@UseOrganizationDomains BIT = 0,
@UseAdminSponsoredFamilies BIT = 0 @UseAdminSponsoredFamilies BIT = 0
AS AS
BEGIN BEGIN
@ -120,6 +121,7 @@ BEGIN
[AllowAdminAccessToAllCollectionItems], [AllowAdminAccessToAllCollectionItems],
[UseRiskInsights], [UseRiskInsights],
[LimitItemDeletion], [LimitItemDeletion],
[UseOrganizationDomains],
[UseAdminSponsoredFamilies] [UseAdminSponsoredFamilies]
) )
VALUES VALUES
@ -181,6 +183,7 @@ BEGIN
@AllowAdminAccessToAllCollectionItems, @AllowAdminAccessToAllCollectionItems,
@UseRiskInsights, @UseRiskInsights,
@LimitItemDeletion, @LimitItemDeletion,
@UseOrganizationDomains,
@UseAdminSponsoredFamilies @UseAdminSponsoredFamilies
) )
END END

View File

@ -26,6 +26,7 @@ BEGIN
[AllowAdminAccessToAllCollectionItems], [AllowAdminAccessToAllCollectionItems],
[UseRiskInsights], [UseRiskInsights],
[LimitItemDeletion], [LimitItemDeletion],
[UseOrganizationDomains],
[UseAdminSponsoredFamilies] [UseAdminSponsoredFamilies]
FROM FROM
[dbo].[Organization] [dbo].[Organization]

View File

@ -56,6 +56,7 @@ CREATE PROCEDURE [dbo].[Organization_Update]
@AllowAdminAccessToAllCollectionItems BIT = 0, @AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0, @UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0, @LimitItemDeletion BIT = 0,
@UseOrganizationDomains BIT = 0,
@UseAdminSponsoredFamilies BIT = 0 @UseAdminSponsoredFamilies BIT = 0
AS AS
BEGIN BEGIN
@ -120,6 +121,7 @@ BEGIN
[AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems, [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems,
[UseRiskInsights] = @UseRiskInsights, [UseRiskInsights] = @UseRiskInsights,
[LimitItemDeletion] = @LimitItemDeletion, [LimitItemDeletion] = @LimitItemDeletion,
[UseOrganizationDomains] = @UseOrganizationDomains,
[UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies [UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies
WHERE WHERE
[Id] = @Id [Id] = @Id

View File

@ -56,6 +56,7 @@ CREATE TABLE [dbo].[Organization] (
[LimitItemDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitItemDeletion] DEFAULT (0), [LimitItemDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitItemDeletion] DEFAULT (0),
[AllowAdminAccessToAllCollectionItems] BIT NOT NULL CONSTRAINT [DF_Organization_AllowAdminAccessToAllCollectionItems] DEFAULT (0), [AllowAdminAccessToAllCollectionItems] BIT NOT NULL CONSTRAINT [DF_Organization_AllowAdminAccessToAllCollectionItems] DEFAULT (0),
[UseRiskInsights] BIT NOT NULL CONSTRAINT [DF_Organization_UseRiskInsights] DEFAULT (0), [UseRiskInsights] BIT NOT NULL CONSTRAINT [DF_Organization_UseRiskInsights] DEFAULT (0),
[UseOrganizationDomains] BIT NOT NULL CONSTRAINT [DF_Organization_UseOrganizationDomains] DEFAULT (0),
[UseAdminSponsoredFamilies] BIT NOT NULL CONSTRAINT [DF_Organization_UseAdminSponsoredFamilies] DEFAULT (0), [UseAdminSponsoredFamilies] BIT NOT NULL CONSTRAINT [DF_Organization_UseAdminSponsoredFamilies] DEFAULT (0),
CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC) CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC)
); );

View File

@ -50,8 +50,9 @@ SELECT
O.[LimitCollectionDeletion], O.[LimitCollectionDeletion],
O.[AllowAdminAccessToAllCollectionItems], O.[AllowAdminAccessToAllCollectionItems],
O.[UseRiskInsights], O.[UseRiskInsights],
O.[UseAdminSponsoredFamilies],
O.[LimitItemDeletion], O.[LimitItemDeletion],
O.[UseAdminSponsoredFamilies],
O.[UseOrganizationDomains],
OS.[IsAdminInitiated] OS.[IsAdminInitiated]
FROM FROM
[dbo].[OrganizationUser] OU [dbo].[OrganizationUser] OU

View File

@ -1,4 +1,4 @@
CREATE VIEW [dbo].[OrganizationView] CREATE VIEW [dbo].[OrganizationView]
AS AS
SELECT SELECT
* *

View File

@ -38,7 +38,8 @@ SELECT
O.[UseRiskInsights], O.[UseRiskInsights],
O.[UseAdminSponsoredFamilies], O.[UseAdminSponsoredFamilies],
P.[Type] ProviderType, P.[Type] ProviderType,
O.[LimitItemDeletion] O.[LimitItemDeletion],
O.[UseOrganizationDomains]
FROM FROM
[dbo].[ProviderUser] PU [dbo].[ProviderUser] PU
INNER JOIN INNER JOIN

View File

@ -193,49 +193,6 @@ public class CiphersControllerTests
} }
} }
[Theory]
[BitAutoData(false)]
[BitAutoData(false)]
[BitAutoData(true)]
public async Task CanEditCiphersAsAdminAsync_Providers(
bool restrictProviders, CipherDetails cipherDetails, CurrentContextOrganization organization, Guid userId, SutProvider<CiphersController> sutProvider
)
{
cipherDetails.OrganizationId = organization.Id;
// Simulate that the user is a provider for the organization
sutProvider.GetDependency<ICurrentContext>().EditAnyCollection(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organization.Id).Returns(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(new List<Cipher> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organization.Id).Returns(new OrganizationAbility
{
Id = organization.Id,
AllowAdminAccessToAllCollectionItems = false
});
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(restrictProviders);
// Non restricted providers should succeed
if (!restrictProviders)
{
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().ReceivedWithAnyArgs()
.DeleteAsync(default, default);
}
else // Otherwise, they should fail
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipherDetails.Id));
await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default, default);
}
await sutProvider.GetDependency<ICurrentContext>().Received().ProviderUserForOrgAsync(organization.Id);
}
[Theory] [Theory]
[BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Admin)]
@ -456,24 +413,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteAdmin_WithProviderUser_DeletesCipher( public async Task DeleteAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).DeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task DeleteAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider) Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipher.OrganizationId = Guid.NewGuid(); cipher.OrganizationId = Guid.NewGuid();
@ -481,7 +421,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher); sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id));
} }
@ -737,43 +676,13 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteManyAdmin_WithProviderUser_DeletesCiphers( public async Task DeleteManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(ciphers);
await sutProvider.Sut.DeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.DeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organizationId, true);
}
[Theory]
[BitAutoData]
public async Task DeleteManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
var organizationId = Guid.NewGuid(); var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString(); model.OrganizationId = organizationId.ToString();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model));
} }
@ -1000,24 +909,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutDeleteAdmin_WithProviderUser_SoftDeletesCipher( public async Task PutDeleteAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
await sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).SoftDeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task PutDeleteAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider) Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipher.OrganizationId = Guid.NewGuid(); cipher.OrganizationId = Guid.NewGuid();
@ -1025,7 +917,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher); sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipher.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipher.Id));
} }
@ -1272,43 +1163,13 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutDeleteManyAdmin_WithProviderUser_SoftDeletesCiphers( public async Task PutDeleteManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(ciphers);
await sutProvider.Sut.PutDeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.SoftDeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organizationId, true);
}
[Theory]
[BitAutoData]
public async Task PutDeleteManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
var organizationId = Guid.NewGuid(); var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString(); model.OrganizationId = organizationId.ToString();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model));
} }
@ -1546,27 +1407,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutRestoreAdmin_WithProviderUser_RestoresCipher( public async Task PutRestoreAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
cipherDetails.Type = CipherType.Login;
cipherDetails.Data = JsonSerializer.Serialize(new CipherLoginData());
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
var result = await sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id);
Assert.IsType<CipherMiniResponseModel>(result);
await sutProvider.GetDependency<ICipherService>().Received(1).RestoreAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task PutRestoreAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider) CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipherDetails.OrganizationId = Guid.NewGuid(); cipherDetails.OrganizationId = Guid.NewGuid();
@ -1574,7 +1415,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetOrganizationDetailsByIdAsync(cipherDetails.Id).Returns(cipherDetails); sutProvider.GetDependency<ICipherRepository>().GetOrganizationDetailsByIdAsync(cipherDetails.Id).Returns(cipherDetails);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id));
} }
@ -1896,49 +1736,12 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutRestoreManyAdmin_WithProviderUser_RestoresCiphers( public async Task PutRestoreManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkRestoreRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = Guid.NewGuid();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(model.OrganizationId).Returns(ciphers);
var cipherOrgDetails = ciphers.Select(c => new CipherOrganizationDetails
{
Id = c.Id,
OrganizationId = model.OrganizationId
}).ToList();
sutProvider.GetDependency<ICipherService>()
.RestoreManyAsync(
Arg.Any<HashSet<Guid>>(),
userId, model.OrganizationId, true)
.Returns(cipherOrgDetails);
var result = await sutProvider.Sut.PutRestoreManyAdmin(model);
Assert.NotNull(result);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.RestoreManyAsync(
Arg.Is<HashSet<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count == model.Ids.Count()),
userId, model.OrganizationId, true);
}
[Theory]
[BitAutoData]
public async Task PutRestoreManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkRestoreRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkRestoreRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
model.OrganizationId = Guid.NewGuid(); model.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreManyAdmin(model));
} }

View File

@ -226,6 +226,11 @@ public class RegisterUserCommandTests
await sutProvider.GetDependency<IReferenceEventService>() await sutProvider.GetDependency<IReferenceEventService>()
.Received(1) .Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(refEvent => refEvent.Type == ReferenceEventType.Signup && refEvent.SignupInitiationPath == default)); .RaiseEventAsync(Arg.Is<ReferenceEvent>(refEvent => refEvent.Type == ReferenceEventType.Signup && refEvent.SignupInitiationPath == default));
// Even if user doesn't have reference data, we should send them welcome email
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendWelcomeEmailAsync(user);
} }
Assert.True(result.Succeeded); Assert.True(result.Succeeded);

View File

@ -76,6 +76,7 @@ public static class OrganizationLicenseFileFixtures
MaxCollections = 2, MaxCollections = 2,
UsePolicies = true, UsePolicies = true,
UseSso = true, UseSso = true,
UseOrganizationDomains = true,
UseKeyConnector = true, UseKeyConnector = true,
UseScim = true, UseScim = true,
UseGroups = true, UseGroups = true,

View File

@ -86,7 +86,8 @@ public class UpdateOrganizationLicenseCommandTests
"Id", "MaxStorageGb", "Issued", "Refresh", "Version", "Trial", "LicenseType", "Id", "MaxStorageGb", "Issued", "Refresh", "Version", "Trial", "LicenseType",
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires", "Hash", "Signature", "SignatureBytes", "InstallationId", "Expires",
"ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion", "ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion",
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems") && "LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems",
"UseOrganizationDomains", "UseAdminSponsoredFamilies") &&
// Same property but different name, use explicit mapping // Same property but different name, use explicit mapping
org.ExpirationDate == license.Expires)); org.ExpirationDate == license.Expires));
} }

View File

@ -0,0 +1,364 @@
/* adds new column "UseOrganizationDomains" not nullable with default of 0 */
ALTER TABLE [dbo].[Organization] ADD [UseOrganizationDomains] bit NOT NULL CONSTRAINT [DF_Organization_UseOrganizationDomains] default (0)
GO
/* add column to Organization_Create*/
CREATE OR ALTER PROCEDURE [dbo].[Organization_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@Identifier NVARCHAR(50),
@Name NVARCHAR(50),
@BusinessName NVARCHAR(50),
@BusinessAddress1 NVARCHAR(50),
@BusinessAddress2 NVARCHAR(50),
@BusinessAddress3 NVARCHAR(50),
@BusinessCountry VARCHAR(2),
@BusinessTaxNumber NVARCHAR(30),
@BillingEmail NVARCHAR(256),
@Plan NVARCHAR(50),
@PlanType TINYINT,
@Seats INT,
@MaxCollections SMALLINT,
@UsePolicies BIT,
@UseSso BIT,
@UseGroups BIT,
@UseDirectory BIT,
@UseEvents BIT,
@UseTotp BIT,
@Use2fa BIT,
@UseApi BIT,
@UseResetPassword BIT,
@SelfHost BIT,
@UsersGetPremium BIT,
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@Enabled BIT,
@LicenseKey VARCHAR(100),
@PublicKey VARCHAR(MAX),
@PrivateKey VARCHAR(MAX),
@TwoFactorProviders NVARCHAR(MAX),
@ExpirationDate DATETIME2(7),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@OwnersNotifiedOfAutoscaling DATETIME2(7),
@MaxAutoscaleSeats INT,
@UseKeyConnector BIT = 0,
@UseScim BIT = 0,
@UseCustomPermissions BIT = 0,
@UseSecretsManager BIT = 0,
@Status TINYINT = 0,
@UsePasswordManager BIT = 1,
@SmSeats INT = null,
@SmServiceAccounts INT = null,
@MaxAutoscaleSmSeats INT= null,
@MaxAutoscaleSmServiceAccounts INT = null,
@SecretsManagerBeta BIT = 0,
@LimitCollectionCreation BIT = NULL,
@LimitCollectionDeletion BIT = NULL,
@AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0,
@UseOrganizationDomains BIT = 0,
@UseAdminSponsoredFamilies BIT = 0
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Organization]
(
[Id],
[Identifier],
[Name],
[BusinessName],
[BusinessAddress1],
[BusinessAddress2],
[BusinessAddress3],
[BusinessCountry],
[BusinessTaxNumber],
[BillingEmail],
[Plan],
[PlanType],
[Seats],
[MaxCollections],
[UsePolicies],
[UseSso],
[UseGroups],
[UseDirectory],
[UseEvents],
[UseTotp],
[Use2fa],
[UseApi],
[UseResetPassword],
[SelfHost],
[UsersGetPremium],
[Storage],
[MaxStorageGb],
[Gateway],
[GatewayCustomerId],
[GatewaySubscriptionId],
[ReferenceData],
[Enabled],
[LicenseKey],
[PublicKey],
[PrivateKey],
[TwoFactorProviders],
[ExpirationDate],
[CreationDate],
[RevisionDate],
[OwnersNotifiedOfAutoscaling],
[MaxAutoscaleSeats],
[UseKeyConnector],
[UseScim],
[UseCustomPermissions],
[UseSecretsManager],
[Status],
[UsePasswordManager],
[SmSeats],
[SmServiceAccounts],
[MaxAutoscaleSmSeats],
[MaxAutoscaleSmServiceAccounts],
[SecretsManagerBeta],
[LimitCollectionCreation],
[LimitCollectionDeletion],
[AllowAdminAccessToAllCollectionItems],
[UseRiskInsights],
[LimitItemDeletion],
[UseOrganizationDomains],
[UseAdminSponsoredFamilies]
)
VALUES
(
@Id,
@Identifier,
@Name,
@BusinessName,
@BusinessAddress1,
@BusinessAddress2,
@BusinessAddress3,
@BusinessCountry,
@BusinessTaxNumber,
@BillingEmail,
@Plan,
@PlanType,
@Seats,
@MaxCollections,
@UsePolicies,
@UseSso,
@UseGroups,
@UseDirectory,
@UseEvents,
@UseTotp,
@Use2fa,
@UseApi,
@UseResetPassword,
@SelfHost,
@UsersGetPremium,
@Storage,
@MaxStorageGb,
@Gateway,
@GatewayCustomerId,
@GatewaySubscriptionId,
@ReferenceData,
@Enabled,
@LicenseKey,
@PublicKey,
@PrivateKey,
@TwoFactorProviders,
@ExpirationDate,
@CreationDate,
@RevisionDate,
@OwnersNotifiedOfAutoscaling,
@MaxAutoscaleSeats,
@UseKeyConnector,
@UseScim,
@UseCustomPermissions,
@UseSecretsManager,
@Status,
@UsePasswordManager,
@SmSeats,
@SmServiceAccounts,
@MaxAutoscaleSmSeats,
@MaxAutoscaleSmServiceAccounts,
@SecretsManagerBeta,
@LimitCollectionCreation,
@LimitCollectionDeletion,
@AllowAdminAccessToAllCollectionItems,
@UseRiskInsights,
@LimitItemDeletion,
@UseOrganizationDomains,
@UseAdminSponsoredFamilies
)
END
GO
/* add column to Organization_ReadAbilities*/
CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadAbilities]
AS
BEGIN
SET NOCOUNT ON
SELECT
[Id],
[UseEvents],
[Use2fa],
CASE
WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN
1
ELSE
0
END AS [Using2fa],
[UsersGetPremium],
[UseCustomPermissions],
[UseSso],
[UseKeyConnector],
[UseScim],
[UseResetPassword],
[UsePolicies],
[Enabled],
[LimitCollectionCreation],
[LimitCollectionDeletion],
[AllowAdminAccessToAllCollectionItems],
[UseRiskInsights],
[LimitItemDeletion],
[UseOrganizationDomains],
[UseAdminSponsoredFamilies]
FROM
[dbo].[Organization]
END
GO
/* add column to Organization_Update*/
CREATE OR ALTER PROCEDURE [dbo].[Organization_Update]
@Id UNIQUEIDENTIFIER,
@Identifier NVARCHAR(50),
@Name NVARCHAR(50),
@BusinessName NVARCHAR(50),
@BusinessAddress1 NVARCHAR(50),
@BusinessAddress2 NVARCHAR(50),
@BusinessAddress3 NVARCHAR(50),
@BusinessCountry VARCHAR(2),
@BusinessTaxNumber NVARCHAR(30),
@BillingEmail NVARCHAR(256),
@Plan NVARCHAR(50),
@PlanType TINYINT,
@Seats INT,
@MaxCollections SMALLINT,
@UsePolicies BIT,
@UseSso BIT,
@UseGroups BIT,
@UseDirectory BIT,
@UseEvents BIT,
@UseTotp BIT,
@Use2fa BIT,
@UseApi BIT,
@UseResetPassword BIT,
@SelfHost BIT,
@UsersGetPremium BIT,
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@Enabled BIT,
@LicenseKey VARCHAR(100),
@PublicKey VARCHAR(MAX),
@PrivateKey VARCHAR(MAX),
@TwoFactorProviders NVARCHAR(MAX),
@ExpirationDate DATETIME2(7),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@OwnersNotifiedOfAutoscaling DATETIME2(7),
@MaxAutoscaleSeats INT,
@UseKeyConnector BIT = 0,
@UseScim BIT = 0,
@UseCustomPermissions BIT = 0,
@UseSecretsManager BIT = 0,
@Status TINYINT = 0,
@UsePasswordManager BIT = 1,
@SmSeats INT = null,
@SmServiceAccounts INT = null,
@MaxAutoscaleSmSeats INT = null,
@MaxAutoscaleSmServiceAccounts INT = null,
@SecretsManagerBeta BIT = 0,
@LimitCollectionCreation BIT = null,
@LimitCollectionDeletion BIT = null,
@AllowAdminAccessToAllCollectionItems BIT = 0,
@UseRiskInsights BIT = 0,
@LimitItemDeletion BIT = 0,
@UseOrganizationDomains BIT = 0,
@UseAdminSponsoredFamilies BIT = 0
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[Organization]
SET
[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,
[UseGroups] = @UseGroups,
[UseDirectory] = @UseDirectory,
[UseEvents] = @UseEvents,
[UseTotp] = @UseTotp,
[Use2fa] = @Use2fa,
[UseApi] = @UseApi,
[UseResetPassword] = @UseResetPassword,
[SelfHost] = @SelfHost,
[UsersGetPremium] = @UsersGetPremium,
[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,
[OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling,
[MaxAutoscaleSeats] = @MaxAutoscaleSeats,
[UseKeyConnector] = @UseKeyConnector,
[UseScim] = @UseScim,
[UseCustomPermissions] = @UseCustomPermissions,
[UseSecretsManager] = @UseSecretsManager,
[Status] = @Status,
[UsePasswordManager] = @UsePasswordManager,
[SmSeats] = @SmSeats,
[SmServiceAccounts] = @SmServiceAccounts,
[MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats,
[MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts,
[SecretsManagerBeta] = @SecretsManagerBeta,
[LimitCollectionCreation] = @LimitCollectionCreation,
[LimitCollectionDeletion] = @LimitCollectionDeletion,
[AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems,
[UseRiskInsights] = @UseRiskInsights,
[LimitItemDeletion] = @LimitItemDeletion,
[UseOrganizationDomains] = @UseOrganizationDomains,
[UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies
WHERE
[Id] = @Id
END
GO

View File

@ -0,0 +1,131 @@
CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView]
AS
SELECT
OU.[UserId],
OU.[OrganizationId],
OU.[Id] OrganizationUserId,
O.[Name],
O.[Enabled],
O.[PlanType],
O.[UsePolicies],
O.[UseSso],
O.[UseKeyConnector],
O.[UseScim],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[UseResetPassword],
O.[SelfHost],
O.[UsersGetPremium],
O.[UseCustomPermissions],
O.[UseSecretsManager],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
OU.[Key],
OU.[ResetPasswordKey],
O.[PublicKey],
O.[PrivateKey],
OU.[Status],
OU.[Type],
SU.[ExternalId] SsoExternalId,
OU.[Permissions],
PO.[ProviderId],
P.[Name] ProviderName,
P.[Type] ProviderType,
SS.[Data] SsoConfig,
OS.[FriendlyName] FamilySponsorshipFriendlyName,
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,
OS.[ToDelete] FamilySponsorshipToDelete,
OS.[ValidUntil] FamilySponsorshipValidUntil,
OU.[AccessSecretsManager],
O.[UsePasswordManager],
O.[SmSeats],
O.[SmServiceAccounts],
O.[LimitCollectionCreation],
O.[LimitCollectionDeletion],
O.[AllowAdminAccessToAllCollectionItems],
O.[UseRiskInsights],
O.[LimitItemDeletion],
O.[UseAdminSponsoredFamilies],
O.[UseOrganizationDomains],
OS.[IsAdminInitiated]
FROM
[dbo].[OrganizationUser] OU
LEFT JOIN
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
LEFT JOIN
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
LEFT JOIN
[dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
LEFT JOIN
[dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
LEFT JOIN
[dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId]
LEFT JOIN
[dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id]
GO
CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView]
AS
SELECT
PU.[UserId],
PO.[OrganizationId],
O.[Name],
O.[Enabled],
O.[UsePolicies],
O.[UseSso],
O.[UseKeyConnector],
O.[UseScim],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[UseResetPassword],
O.[SelfHost],
O.[UsersGetPremium],
O.[UseCustomPermissions],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
PO.[Key],
O.[PublicKey],
O.[PrivateKey],
PU.[Status],
PU.[Type],
PO.[ProviderId],
PU.[Id] ProviderUserId,
P.[Name] ProviderName,
O.[PlanType],
O.[LimitCollectionCreation],
O.[LimitCollectionDeletion],
O.[AllowAdminAccessToAllCollectionItems],
O.[UseRiskInsights],
O.[UseAdminSponsoredFamilies],
P.[Type] ProviderType,
O.[LimitItemDeletion],
O.[UseOrganizationDomains]
FROM
[dbo].[ProviderUser] PU
INNER JOIN
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
INNER JOIN
[dbo].[Provider] P ON P.[Id] = PU.[ProviderId]
GO
CREATE OR ALTER VIEW [dbo].[OrganizationView]
AS
SELECT
*
FROM
[dbo].[Organization]
GO

View File

@ -0,0 +1,9 @@
/* update the new column to have the value used in UseSso to preserve existing orgs ability */
UPDATE
[dbo].[Organization]
SET
[UseOrganizationDomains] = [UseSso]
WHERE
[UseSso] = 1
GO

View File

@ -0,0 +1,3 @@
UPDATE Organization
SET UseOrganizationDomains = UseSso
WHERE UseSso = 1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomains : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UseOrganizationDomains",
table: "Organization",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration.");
}
}

View File

@ -0,0 +1,23 @@
using Bit.Core.Utilities;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomainsData : Migration
{
private const string _addUseOrganizationDomainsMigrationScript = "MySqlMigrations.HelperScripts.2025-05-13_00_AddUseOrganizationDomains.sql";
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(CoreHelpers.GetEmbeddedResourceContentsAsync(_addUseOrganizationDomainsMigrationScript));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration");
}
}

View File

@ -185,6 +185,9 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<bool>("UseKeyConnector") b.Property<bool>("UseKeyConnector")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<bool>("UseOrganizationDomains")
.HasColumnType("tinyint(1)");
b.Property<bool>("UsePasswordManager") b.Property<bool>("UsePasswordManager")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<UserSecretsId>9f1cd3e0-70f2-4921-8068-b2538fd7c3f7</UserSecretsId> <UserSecretsId>9f1cd3e0-70f2-4921-8068-b2538fd7c3f7</UserSecretsId>
@ -32,5 +32,6 @@
<EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.sql" /> <EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.sql" />
<EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.sql" /> <EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.sql" />
<EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.sql" /> <EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.sql" />
<EmbeddedResource Include="HelperScripts\2025-05-13_00_AddUseOrganizationDomains.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,3 @@
UPDATE "Organization"
SET "UseOrganizationDomains" = "UseSso"
WHERE "UseSso" IS true

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomains : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UseOrganizationDomains",
table: "Organization",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration.");
}
}

View File

@ -0,0 +1,25 @@
using Bit.Core.Utilities;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomainsData : Migration
{
private const string _addUseOrganizationDomainsMigrationScript = "PostgresMigrations.HelperScripts.2025-05-13_00_AddUseOrganizationDomains.psql";
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(CoreHelpers.GetEmbeddedResourceContentsAsync(_addUseOrganizationDomainsMigrationScript));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration.");
}
}

View File

@ -187,6 +187,9 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<bool>("UseKeyConnector") b.Property<bool>("UseKeyConnector")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<bool>("UseOrganizationDomains")
.HasColumnType("boolean");
b.Property<bool>("UsePasswordManager") b.Property<bool>("UsePasswordManager")
.HasColumnType("boolean"); .HasColumnType("boolean");

View File

@ -27,5 +27,6 @@
<EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.psql" /> <EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.psql" />
<EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.psql" /> <EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.psql" />
<EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.psql" /> <EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.psql" />
<EmbeddedResource Include="HelperScripts\2025-05-13_00_AddUseOrganizationDomains.psql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,3 @@
UPDATE [Organization]
SET [UseOrganizationDomains] = [UseSso]
WHERE [UseSso] = 1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomains : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UseOrganizationDomains",
table: "Organization",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration.");
}
}

View File

@ -0,0 +1,25 @@
using Bit.Core.Utilities;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class AddUseOrganizationDomainsData : Migration
{
private const string _addUseOrganizationDomainsMigrationScript = "SqliteMigrations.HelperScripts.2025-05-13_00_AddUseOrganizationDomains.sql";
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(CoreHelpers.GetEmbeddedResourceContentsAsync(_addUseOrganizationDomainsMigrationScript));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
throw new Exception("Irreversible migration.");
}
}

View File

@ -180,6 +180,9 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<bool>("UseKeyConnector") b.Property<bool>("UseKeyConnector")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("UseOrganizationDomains")
.HasColumnType("INTEGER");
b.Property<bool>("UsePasswordManager") b.Property<bool>("UsePasswordManager")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@ -27,6 +27,7 @@
<EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.sql" /> <EmbeddedResource Include="HelperScripts\2024-04-25_00_EnableOrgsCollectionEnhancements.sql" />
<EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.sql" /> <EmbeddedResource Include="HelperScripts\2024-08-26_00_FinalFlexibleCollectionsDataMigrations.sql" />
<EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.sql" /> <EmbeddedResource Include="HelperScripts\2024-09-05_00_SyncDuoVersionFourMetadataToVersionTwo.sql" />
<EmbeddedResource Include="HelperScripts\2025-05-13_00_AddUseOrganizationDomains.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>