1
0
mirror of https://github.com/bitwarden/server synced 2025-12-26 05:03:18 +00:00

Merge branch 'main' into billing/PM-24964/msp-unable-verfy-bank-account

This commit is contained in:
Alex Morask
2025-09-05 08:46:39 -05:00
73 changed files with 494 additions and 103 deletions

13
.github/workflows/load-test.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Test Stub
on:
workflow_dispatch:
jobs:
test:
permissions:
contents: read
name: Test
runs-on: ubuntu-24.04
steps:
- name: Test
run: exit 0

View File

@@ -82,7 +82,7 @@ jobs:
version: ${{ inputs.version_number_override }}
- name: Generate GH App token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
@@ -200,7 +200,7 @@ jobs:
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}

View File

@@ -1,9 +1,9 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities;

View File

@@ -4,6 +4,7 @@
using System.Security.Cryptography.X509Certificates;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.Settings;
@@ -416,7 +417,7 @@ public class DynamicAuthenticationSchemeProvider : AuthenticationSchemeProvider
SPOptions = spOptions,
SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme,
SignOutScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme,
CookieManager = new IdentityServer.DistributedCacheCookieManager(),
CookieManager = new DistributedCacheCookieManager(),
};
options.IdentityProviders.Add(idp);

View File

@@ -1,9 +1,9 @@
#nullable enable
using System.Security.Claims;
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Models.Data;
namespace Bit.Api.AdminConsole.Authorization;

View File

@@ -554,18 +554,12 @@ public class OrganizationsController : Controller
[HttpPut("{id}/collection-management")]
public async Task<OrganizationResponseModel> PutCollectionManagement(Guid id, [FromBody] OrganizationCollectionManagementUpdateRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationOwner(id))
{
throw new NotFoundException();
}
await _organizationService.UpdateAsync(model.ToOrganization(organization, _featureService), eventType: EventType.Organization_CollectionManagement_Updated);
var organization = await _organizationService.UpdateCollectionManagementSettingsAsync(id, model.ToSettings());
var plan = await _pricingClient.GetPlan(organization.PlanType);
return new OrganizationResponseModel(organization, plan);
}

View File

@@ -78,12 +78,14 @@ public class ProfileOrganizationResponseModel : ResponseModel
UseRiskInsights = organization.UseRiskInsights;
UseOrganizationDomains = organization.UseOrganizationDomains;
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
SsoEnabled = organization.SsoEnabled ?? false;
if (organization.SsoConfig != null)
{
var ssoConfigData = SsoConfigurationData.Deserialize(organization.SsoConfig);
KeyConnectorEnabled = ssoConfigData.MemberDecryptionType == MemberDecryptionType.KeyConnector && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl);
KeyConnectorUrl = ssoConfigData.KeyConnectorUrl;
SsoMemberDecryptionType = ssoConfigData.MemberDecryptionType;
}
}
@@ -160,4 +162,6 @@ public class ProfileOrganizationResponseModel : ResponseModel
public bool UseOrganizationDomains { get; set; }
public bool UseAdminSponsoredFamilies { get; set; }
public bool IsAdminInitiated { get; set; }
public bool SsoEnabled { get; set; }
public MemberDecryptionType? SsoMemberDecryptionType { get; set; }
}

View File

@@ -1,5 +1,4 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Services;
using Bit.Core.AdminConsole.Models.Business;
namespace Bit.Api.Models.Request.Organizations;
@@ -10,12 +9,11 @@ public class OrganizationCollectionManagementUpdateRequestModel
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public virtual Organization ToOrganization(Organization existingOrganization, IFeatureService featureService)
public OrganizationCollectionManagementSettings ToSettings() => new()
{
existingOrganization.LimitCollectionCreation = LimitCollectionCreation;
existingOrganization.LimitCollectionDeletion = LimitCollectionDeletion;
existingOrganization.LimitItemDeletion = LimitItemDeletion;
existingOrganization.AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems;
return existingOrganization;
}
LimitCollectionCreation = LimitCollectionCreation,
LimitCollectionDeletion = LimitCollectionDeletion,
LimitItemDeletion = LimitItemDeletion,
AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems
};
}

View File

@@ -4,10 +4,10 @@
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities;

View File

@@ -4,10 +4,10 @@
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;

View File

@@ -1,8 +1,8 @@
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;

View File

@@ -13,7 +13,6 @@ using Bit.Api.KeyManagement.Validators;
using Bit.Api.Tools.Models.Request;
using Bit.Api.Vault.Models.Request;
using Bit.Core.Auth.Entities;
using Bit.Core.IdentityServer;
using Bit.SharedWeb.Health;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi.Models;
@@ -33,6 +32,8 @@ using Bit.Core.Tools.ImportFeatures;
using Bit.Core.Auth.Models.Api.Request;
using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Tools.SendFeatures;
using Bit.Core.Auth.IdentityServer;
#if !OSS
using Bit.Commercial.Core.SecretsManager;

View File

@@ -2,7 +2,7 @@
using Bit.Api.Tools.Authorization;
using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.PhishingDomainFeatures;
using Bit.Core.PhishingDomainFeatures.Interfaces;
using Bit.Core.Repositories;

View File

@@ -152,6 +152,12 @@ public class FreshdeskController : Controller
return new BadRequestResult();
}
// if there is no description, then we don't send anything to onyx
if (string.IsNullOrEmpty(model.TicketDescriptionText.Trim()))
{
return Ok();
}
// create the onyx `answer-with-citation` request
var onyxRequestModel = new OnyxAnswerWithCitationRequestModel(model.TicketDescriptionText, _billingSettings.Onyx.PersonaId);
var onyxRequest = new HttpRequestMessage(HttpMethod.Post,
@@ -164,9 +170,12 @@ public class FreshdeskController : Controller
// the CallOnyxApi will return a null if we have an error response
if (onyxJsonResponse?.Answer == null || !string.IsNullOrEmpty(onyxJsonResponse?.ErrorMsg))
{
return BadRequest(
string.Format("Failed to get a valid response from Onyx API. Response: {0}",
JsonSerializer.Serialize(onyxJsonResponse ?? new OnyxAnswerWithCitationResponseModel())));
_logger.LogWarning("Error getting answer from Onyx AI. Freshdesk model: {model}\r\n Onyx query {query}\r\nresponse: {response}. ",
JsonSerializer.Serialize(model),
JsonSerializer.Serialize(onyxRequestModel),
JsonSerializer.Serialize(onyxJsonResponse));
return Ok(); // return ok so we don't retry
}
// add the answer as a note to the ticket

View File

@@ -70,7 +70,16 @@ public enum EventType : int
Organization_EnabledKeyConnector = 1606,
Organization_DisabledKeyConnector = 1607,
Organization_SponsorshipsSynced = 1608,
Organization_CollectionManagement_Updated = 1609,
[Obsolete("Use other specific Organization_CollectionManagement events instead")]
Organization_CollectionManagement_Updated = 1609, // TODO: Will be removed in PM-25315
Organization_CollectionManagement_LimitCollectionCreationEnabled = 1610,
Organization_CollectionManagement_LimitCollectionCreationDisabled = 1611,
Organization_CollectionManagement_LimitCollectionDeletionEnabled = 1612,
Organization_CollectionManagement_LimitCollectionDeletionDisabled = 1613,
Organization_CollectionManagement_LimitItemDeletionEnabled = 1614,
Organization_CollectionManagement_LimitItemDeletionDisabled = 1615,
Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled = 1616,
Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled = 1617,
Policy_Updated = 1700,

View File

@@ -0,0 +1,9 @@
namespace Bit.Core.AdminConsole.Models.Business;
public record OrganizationCollectionManagementSettings
{
public bool LimitCollectionCreation { get; set; }
public bool LimitCollectionDeletion { get; set; }
public bool LimitItemDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
}

View File

@@ -49,6 +49,7 @@ public class OrganizationUserOrganizationDetails
public string ProviderName { get; set; }
public ProviderType? ProviderType { get; set; }
public string FamilySponsorshipFriendlyName { get; set; }
public bool? SsoEnabled { get; set; }
public string SsoConfig { get; set; }
public DateTime? FamilySponsorshipLastSyncDate { get; set; }
public DateTime? FamilySponsorshipValidUntil { get; set; }

View File

@@ -1,5 +1,5 @@
using System.Text.Json.Serialization;
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
namespace Bit.Core.Models.Data;

View File

@@ -2,6 +2,7 @@
#nullable disable
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -19,7 +20,8 @@ public interface IOrganizationService
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated);
Task UpdateAsync(Organization organization, bool updateBilling = false);
Task<Organization> UpdateCollectionManagementSettingsAsync(Guid organizationId, OrganizationCollectionManagementSettings settings);
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser,

View File

@@ -5,6 +5,7 @@ using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@@ -378,8 +379,7 @@ public class OrganizationService : IOrganizationService
}
}
public async Task UpdateAsync(Organization organization, bool updateBilling = false,
EventType eventType = EventType.Organization_Updated)
public async Task UpdateAsync(Organization organization, bool updateBilling = false)
{
if (organization.Id == default(Guid))
{
@@ -395,7 +395,7 @@ public class OrganizationService : IOrganizationService
}
}
await ReplaceAndUpdateCacheAsync(organization, eventType);
await ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{
@@ -420,11 +420,35 @@ public class OrganizationService : IOrganizationService
},
});
}
}
if (eventType == EventType.Organization_CollectionManagement_Updated)
public async Task<Organization> UpdateCollectionManagementSettingsAsync(Guid organizationId, OrganizationCollectionManagementSettings settings)
{
var existingOrganization = await _organizationRepository.GetByIdAsync(organizationId);
if (existingOrganization == null)
{
await _pushNotificationService.PushSyncOrganizationCollectionManagementSettingsAsync(organization);
throw new NotFoundException();
}
// Create logging actions based on what will change
var loggingActions = CreateCollectionManagementLoggingActions(existingOrganization, settings);
existingOrganization.LimitCollectionCreation = settings.LimitCollectionCreation;
existingOrganization.LimitCollectionDeletion = settings.LimitCollectionDeletion;
existingOrganization.LimitItemDeletion = settings.LimitItemDeletion;
existingOrganization.AllowAdminAccessToAllCollectionItems = settings.AllowAdminAccessToAllCollectionItems;
existingOrganization.RevisionDate = DateTime.UtcNow;
await ReplaceAndUpdateCacheAsync(existingOrganization);
if (loggingActions.Any())
{
await Task.WhenAll(loggingActions.Select(action => action()));
}
await _pushNotificationService.PushSyncOrganizationCollectionManagementSettingsAsync(existingOrganization);
return existingOrganization;
}
public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
@@ -1214,4 +1238,44 @@ public class OrganizationService : IOrganizationService
return status;
}
private List<Func<Task>> CreateCollectionManagementLoggingActions(
Organization existingOrganization, OrganizationCollectionManagementSettings settings)
{
var loggingActions = new List<Func<Task>>();
if (existingOrganization.LimitCollectionCreation != settings.LimitCollectionCreation)
{
var eventType = settings.LimitCollectionCreation
? EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled
: EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}
if (existingOrganization.LimitCollectionDeletion != settings.LimitCollectionDeletion)
{
var eventType = settings.LimitCollectionDeletion
? EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled
: EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}
if (existingOrganization.LimitItemDeletion != settings.LimitItemDeletion)
{
var eventType = settings.LimitItemDeletion
? EventType.Organization_CollectionManagement_LimitItemDeletionEnabled
: EventType.Organization_CollectionManagement_LimitItemDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}
if (existingOrganization.AllowAdminAccessToAllCollectionItems != settings.AllowAdminAccessToAllCollectionItems)
{
var eventType = settings.AllowAdminAccessToAllCollectionItems
? EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled
: EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}
return loggingActions;
}
}

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.Identity;
namespace Bit.Core.Auth.Identity;
public static class Claims
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.Identity;
namespace Bit.Core.Auth.Identity;
public enum IdentityClientType : byte
{

View File

@@ -1,6 +1,6 @@
using Duende.IdentityServer.Models;
namespace Bit.Core.IdentityServer;
namespace Bit.Core.Auth.IdentityServer;
public static class ApiScopes
{

View File

@@ -8,7 +8,7 @@ using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Bit.Core.IdentityServer;
namespace Bit.Core.Auth.IdentityServer;
public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<CookieAuthenticationOptions>
{

View File

@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.IdentityServer;
namespace Bit.Core.Auth.IdentityServer;
public class DistributedCacheCookieManager : ICookieManager
{

View File

@@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Caching.Distributed;
namespace Bit.Core.IdentityServer;
namespace Bit.Core.Auth.IdentityServer;
public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket>
{

View File

@@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Distributed;
namespace Bit.Core.IdentityServer;
namespace Bit.Core.Auth.IdentityServer;
public class DistributedCacheTicketStore : ITicketStore
{

View File

@@ -1,5 +1,5 @@
using System.Security.Claims;
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
namespace Bit.Core.Auth.UserFeatures.SendAccess;

View File

@@ -128,6 +128,7 @@ public static class FeatureFlagKeys
public const string CreateDefaultLocation = "pm-19467-create-default-location";
public const string DirectoryConnectorPreventUserRemoval = "pm-24592-directory-connector-prevent-user-removal";
public const string CipherRepositoryBulkResourceCreation = "pm-24951-cipher-repository-bulk-resource-creation-service";
public const string CollectionVaultRefactor = "pm-25030-resolve-ts-upgrade-errors";
/* Auth Team */
public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence";
@@ -160,7 +161,6 @@ public static class FeatureFlagKeys
public const string TrialPayment = "PM-8163-trial-payment";
public const string PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships";
public const string UsePricingService = "use-pricing-service";
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
public const string UseOrganizationWarningsService = "use-organization-warnings-service";
public const string PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout";

View File

@@ -6,10 +6,10 @@ using Bit.Core.AdminConsole.Context;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Identity;
using Bit.Core.Billing.Extensions;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Settings;

View File

@@ -3,9 +3,9 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.Context;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Identity;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;

View File

@@ -1,4 +1,4 @@
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
namespace Bit.Core.Enums;

View File

@@ -1,9 +1,9 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.IdentityServer;
using Bit.Core.Models.Api.Request.OrganizationSponsorships;
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
using Bit.Core.Models.Data.Organizations.OrganizationSponsorships;

View File

@@ -1,6 +1,6 @@
using Bit.Core.Context;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Models;
using Bit.Core.Models.Api;
using Bit.Core.Repositories;

View File

@@ -1,5 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Platform.Push;
using Bit.Core.Services;

View File

@@ -1,4 +1,4 @@
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces;

View File

@@ -1,8 +1,8 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.Auth.Identity;
using Bit.Core.Context;
using Bit.Core.Identity;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using LaunchDarkly.Logging;

View File

@@ -16,10 +16,10 @@ using Azure.Storage.Queues.Models;
using Bit.Core.AdminConsole.Context;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity;
using Bit.Core.Billing.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Identity;
using Bit.Core.Settings;
using Duende.IdentityModel;
using Microsoft.AspNetCore.DataProtection;

View File

@@ -1,6 +1,6 @@
using System.Globalization;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Context;
using Bit.Core.IdentityServer;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;

View File

@@ -1,5 +1,5 @@
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.IdentityServer;
using Duende.IdentityModel;
using Duende.IdentityServer.Models;

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Platform.Installations;
using Duende.IdentityModel;
using Duende.IdentityServer.Models;

View File

@@ -1,7 +1,7 @@
#nullable enable
using System.Diagnostics;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Settings;
using Duende.IdentityModel;
using Duende.IdentityServer.Models;

View File

@@ -1,9 +1,9 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.Repositories;
using Duende.IdentityModel;
using Duende.IdentityServer.Models;

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Repositories;

View File

@@ -3,9 +3,9 @@
using System.Collections.ObjectModel;
using System.Security.Claims;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Identity;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Duende.IdentityModel;

View File

@@ -1,9 +1,9 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Identity;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;

View File

@@ -8,12 +8,12 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Models.Api.Response;
using Bit.Core.Auth.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Models.Api;
using Bit.Core.Models.Api.Response;
using Bit.Core.Repositories;

View File

@@ -3,11 +3,11 @@ using System.Security.Claims;
using Bit.Core;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.Models.Api.Response;
using Bit.Core.Auth.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.IdentityServer;
using Bit.Core.Platform.Installations;
using Bit.Core.Repositories;
using Bit.Core.Services;

View File

@@ -1,6 +1,6 @@
using System.Security.Claims;
using Bit.Core;
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;

View File

@@ -1,6 +1,6 @@
using System.Security.Claims;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Identity;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Identity.IdentityServer.Enums;

View File

@@ -1,5 +1,5 @@
using System.Security.Claims;
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
using Bit.Core.KeyManagement.Sends;
using Bit.Core.Tools.Models.Data;
using Bit.Identity.IdentityServer.Enums;

View File

@@ -1,5 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.Settings;
using Bit.Identity.IdentityServer.Enums;
using Duende.IdentityServer.Models;

View File

@@ -1,5 +1,5 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.Repositories;
using Bit.Core.Settings;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Utilities;

View File

@@ -56,6 +56,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
ProviderId = p.Id,
ProviderName = p.Name,
ProviderType = p.Type,
SsoEnabled = ss.Enabled,
SsoConfig = ss.Data,
FamilySponsorshipFriendlyName = os.FriendlyName,
FamilySponsorshipLastSyncDate = os.LastSyncDate,

View File

@@ -1,5 +1,5 @@
using System.Globalization;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.SharedWeb.Utilities;

View File

@@ -30,8 +30,6 @@ using Bit.Core.Dirt.Reports.ReportFeatures;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.HostedServices;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.KeyManagement;
using Bit.Core.NotificationCenter;
using Bit.Core.OrganizationFeatures;

View File

@@ -37,6 +37,7 @@ SELECT
PO.[ProviderId],
P.[Name] ProviderName,
P.[Type] ProviderType,
SS.[Enabled] SsoEnabled,
SS.[Data] SsoConfig,
OS.[FriendlyName] FamilySponsorshipFriendlyName,
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,

View File

@@ -2,10 +2,12 @@
using AutoFixture.Xunit2;
using Bit.Api.AdminConsole.Controllers;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.Models.Request.Organizations;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
@@ -29,6 +31,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tokens;
using Bit.Core.Utilities;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider;
using NSubstitute;
using Xunit;
@@ -293,4 +296,40 @@ public class OrganizationsControllerTests : IDisposable
Assert.True(result.ResetPasswordEnabled);
}
[Theory, AutoData]
public async Task PutCollectionManagement_ValidRequest_Success(
Organization organization,
OrganizationCollectionManagementUpdateRequestModel model)
{
// Arrange
_currentContext.OrganizationOwner(organization.Id).Returns(true);
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
_pricingClient.GetPlan(Arg.Any<PlanType>()).Returns(plan);
_organizationService
.UpdateCollectionManagementSettingsAsync(
organization.Id,
Arg.Is<OrganizationCollectionManagementSettings>(s =>
s.LimitCollectionCreation == model.LimitCollectionCreation &&
s.LimitCollectionDeletion == model.LimitCollectionDeletion &&
s.LimitItemDeletion == model.LimitItemDeletion &&
s.AllowAdminAccessToAllCollectionItems == model.AllowAdminAccessToAllCollectionItems))
.Returns(organization);
// Act
await _sut.PutCollectionManagement(organization.Id, model);
// Assert
await _organizationService
.Received(1)
.UpdateCollectionManagementSettingsAsync(
organization.Id,
Arg.Is<OrganizationCollectionManagementSettings>(s =>
s.LimitCollectionCreation == model.LimitCollectionCreation &&
s.LimitCollectionDeletion == model.LimitCollectionDeletion &&
s.LimitItemDeletion == model.LimitItemDeletion &&
s.AllowAdminAccessToAllCollectionItems == model.AllowAdminAccessToAllCollectionItems));
}
}

View File

@@ -8,6 +8,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ReceivedExtensions;
@@ -126,7 +127,7 @@ public class FreshdeskControllerTests
[Theory]
[BitAutoData(WebhookKey)]
public async Task PostWebhookOnyxAi_invalid_onyx_response_results_in_BadRequest(
public async Task PostWebhookOnyxAi_invalid_onyx_response_results_is_logged(
string freshdeskWebhookKey, FreshdeskOnyxAiWebhookModel model,
SutProvider<FreshdeskController> sutProvider)
{
@@ -150,8 +151,18 @@ public class FreshdeskControllerTests
var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model);
var result = Assert.IsAssignableFrom<BadRequestObjectResult>(response);
Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status200OK, statusCodeResult.StatusCode);
var _logger = sutProvider.GetDependency<ILogger<FreshdeskController>>();
// workaround because _logger.Received(1).LogWarning(...) does not work
_logger.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Log" && c.GetArguments()[1].ToString().Contains("Error getting answer from Onyx AI"));
// sent call to Onyx API - but we got an error response
_ = mockOnyxHttpMessageHandler.Received(1).Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>());
// did not call freshdesk to add a note since onyx failed
_ = mockFreshdeskHttpMessageHandler.DidNotReceive().Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>());
}
[Theory]
@@ -174,10 +185,9 @@ public class FreshdeskControllerTests
.Returns(mockFreshdeskAddNoteResponse);
var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler);
// mocking Onyx api response given a ticket description
var mockOnyxHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
onyxResponse.ErrorMsg = string.Empty;
onyxResponse.ErrorMsg = "string.Empty";
var mockOnyxResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(onyxResponse))
@@ -195,6 +205,37 @@ public class FreshdeskControllerTests
Assert.Equal(StatusCodes.Status200OK, result.StatusCode);
}
[Theory]
[BitAutoData(WebhookKey)]
public async Task PostWebhookOnyxAi_ticket_description_is_empty_return_success(
string freshdeskWebhookKey, FreshdeskOnyxAiWebhookModel model,
SutProvider<FreshdeskController> sutProvider)
{
var billingSettings = sutProvider.GetDependency<IOptions<BillingSettings>>().Value;
billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api");
model.TicketDescriptionText = " "; // empty description
// mocking freshdesk api add note request (POST)
var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler);
// mocking Onyx api response given a ticket description
var mockOnyxHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var onyxHttpClient = new HttpClient(mockOnyxHttpMessageHandler);
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("FreshdeskApi").Returns(freshdeskHttpClient);
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("OnyxApi").Returns(onyxHttpClient);
var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model);
var result = Assert.IsAssignableFrom<OkResult>(response);
Assert.Equal(StatusCodes.Status200OK, result.StatusCode);
_ = mockFreshdeskHttpMessageHandler.DidNotReceive().Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>());
_ = mockOnyxHttpMessageHandler.DidNotReceive().Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>());
}
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
@@ -27,7 +28,6 @@ using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Fakes;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NSubstitute.ReceivedExtensions;
using NSubstitute.ReturnsExtensions;
using Stripe;
using Xunit;
@@ -42,8 +42,6 @@ public class OrganizationServiceTests
{
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
[Theory]
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize, BitAutoData]
@@ -1229,6 +1227,109 @@ public class OrganizationServiceTests
.GetByIdentifierAsync(Arg.Is<string>(id => id == organization.Identifier));
}
[Theory]
[BitAutoData(false, true, false, true)]
[BitAutoData(true, false, true, false)]
public async Task UpdateCollectionManagementSettingsAsync_WhenSettingsChanged_LogsSpecificEvents(
bool newLimitCollectionCreation,
bool newLimitCollectionDeletion,
bool newLimitItemDeletion,
bool newAllowAdminAccessToAllCollectionItems,
Organization existingOrganization, SutProvider<OrganizationService> sutProvider)
{
// Arrange
existingOrganization.LimitCollectionCreation = false;
existingOrganization.LimitCollectionDeletion = false;
existingOrganization.LimitItemDeletion = false;
existingOrganization.AllowAdminAccessToAllCollectionItems = false;
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(existingOrganization.Id)
.Returns(existingOrganization);
var settings = new OrganizationCollectionManagementSettings
{
LimitCollectionCreation = newLimitCollectionCreation,
LimitCollectionDeletion = newLimitCollectionDeletion,
LimitItemDeletion = newLimitItemDeletion,
AllowAdminAccessToAllCollectionItems = newAllowAdminAccessToAllCollectionItems
};
// Act
await sutProvider.Sut.UpdateCollectionManagementSettingsAsync(existingOrganization.Id, settings);
// Assert
var eventService = sutProvider.GetDependency<IEventService>();
if (newLimitCollectionCreation)
{
await eventService.Received(1).LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled));
}
else
{
await eventService.DidNotReceive().LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled));
}
if (newLimitCollectionDeletion)
{
await eventService.Received(1).LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled));
}
else
{
await eventService.DidNotReceive().LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled));
}
if (newLimitItemDeletion)
{
await eventService.Received(1).LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitItemDeletionEnabled));
}
else
{
await eventService.DidNotReceive().LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitItemDeletionEnabled));
}
if (newAllowAdminAccessToAllCollectionItems)
{
await eventService.Received(1).LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled));
}
else
{
await eventService.DidNotReceive().LogOrganizationEventAsync(
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled));
}
}
[Theory, BitAutoData]
public async Task UpdateCollectionManagementSettingsAsync_WhenOrganizationNotFound_ThrowsNotFoundException(
Guid organizationId, OrganizationCollectionManagementSettings settings, SutProvider<OrganizationService> sutProvider)
{
// Arrange
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organizationId)
.Returns((Organization)null);
// Act/Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateCollectionManagementSettingsAsync(organizationId, settings));
await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.GetByIdAsync(organizationId);
}
// Must set real guids in order for dictionary of guids to not throw aggregate exceptions
private void SetupOrgUserRepositoryCreateManyAsyncMock(IOrganizationUserRepository organizationUserRepository)
{

View File

@@ -1,6 +1,6 @@
using System.Security.Claims;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.UserFeatures.SendAccess;
using Bit.Core.Identity;
using Xunit;
namespace Bit.Core.Test.Auth.UserFeatures.SendAccess;

View File

@@ -1,6 +1,6 @@
using Bit.Core;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;

View File

@@ -1,6 +1,6 @@
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;

View File

@@ -1,5 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.KeyManagement.Sends;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;

View File

@@ -1,4 +1,4 @@
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Platform.Installations;
using Bit.Identity.IdentityServer.ClientProviders;
using Duende.IdentityModel;

View File

@@ -1,4 +1,4 @@
using Bit.Core.IdentityServer;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Settings;
using Bit.Identity.IdentityServer.ClientProviders;
using Duende.IdentityModel;

View File

@@ -1,8 +1,8 @@
using System.Collections.Specialized;
using Bit.Core;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;

View File

@@ -1,8 +1,8 @@
using System.Collections.Specialized;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Utilities;

View File

@@ -1,8 +1,8 @@
using System.Collections.Specialized;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.UserFeatures.SendAccess;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.KeyManagement.Sends;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Utilities;

View File

@@ -1,8 +1,8 @@
using System.Collections.Specialized;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.UserFeatures.SendAccess;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.KeyManagement.Sends;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Utilities;

View File

@@ -1,6 +1,10 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -442,7 +446,8 @@ public class OrganizationUserRepositoryTests
[DatabaseTheory, DatabaseData]
public async Task GetManyDetailsByUserAsync_Works(IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
IOrganizationUserRepository organizationUserRepository,
ISsoConfigRepository ssoConfigRepository)
{
var user1 = await userRepository.CreateAsync(new User
{
@@ -475,6 +480,18 @@ public class OrganizationUserRepositoryTests
AccessSecretsManager = false
});
var ssoConfigData = new SsoConfigurationData
{
MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption
};
var ssoConfig = await ssoConfigRepository.CreateAsync(new SsoConfig
{
OrganizationId = organization.Id,
Enabled = true,
Data = ssoConfigData.Serialize()
});
var responseModel = await organizationUserRepository.GetManyDetailsByUserAsync(user1.Id);
Assert.NotNull(responseModel);
@@ -487,6 +504,8 @@ public class OrganizationUserRepositoryTests
Assert.Equal(organization.UsePolicies, result.UsePolicies);
Assert.Equal(organization.UseSso, result.UseSso);
Assert.Equal(organization.UseKeyConnector, result.UseKeyConnector);
Assert.Equal(ssoConfig.Enabled, result.SsoEnabled);
Assert.Equal(ssoConfig.Data, result.SsoConfig);
Assert.Equal(organization.UseScim, result.UseScim);
Assert.Equal(organization.UseGroups, result.UseGroups);
Assert.Equal(organization.UseDirectory, result.UseDirectory);

View File

@@ -0,0 +1,86 @@
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.[Enabled] SsoEnabled,
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
--Manually refresh [dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]
IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]') IS NOT NULL
BEGIN
EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]';
END
GO
--Manually refresh [dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]
IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]') IS NOT NULL
BEGIN
EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]';
END
GO