mirror of
https://github.com/bitwarden/server
synced 2026-02-22 04:13:43 +00:00
Merge remote-tracking branch 'origin/main' into ac/pm-23768/server-public-api---add-restore/revoke-for-members
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Collections;
|
||||
|
||||
public static class CollectionUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Arranges Collection and CollectionUser objects to create default user collections.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The organization ID.</param>
|
||||
/// <param name="organizationUserIds">The IDs for organization users who need default collections.</param>
|
||||
/// <param name="defaultCollectionName">The encrypted string to use as the default collection name.</param>
|
||||
/// <returns>A tuple containing the collections and collection users.</returns>
|
||||
public static (ICollection<Collection> collections, ICollection<CollectionUser> collectionUsers)
|
||||
BuildDefaultUserCollections(Guid organizationId, IEnumerable<Guid> organizationUserIds,
|
||||
string defaultCollectionName)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var collectionUsers = new List<CollectionUser>();
|
||||
var collections = new List<Collection>();
|
||||
|
||||
foreach (var orgUserId in organizationUserIds)
|
||||
{
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
|
||||
collections.Add(new Collection
|
||||
{
|
||||
Id = collectionId,
|
||||
OrganizationId = organizationId,
|
||||
Name = defaultCollectionName,
|
||||
CreationDate = now,
|
||||
RevisionDate = now,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultUserCollectionEmail = null
|
||||
|
||||
});
|
||||
|
||||
collectionUsers.Add(new CollectionUser
|
||||
{
|
||||
CollectionId = collectionId,
|
||||
OrganizationUserId = orgUserId,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true,
|
||||
});
|
||||
}
|
||||
|
||||
return (collections, collectionUsers);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -83,19 +81,10 @@ public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserReposi
|
||||
return;
|
||||
}
|
||||
|
||||
await collectionRepository.CreateAsync(
|
||||
new Collection
|
||||
{
|
||||
OrganizationId = request.Organization!.Id,
|
||||
Name = request.DefaultUserCollectionName,
|
||||
Type = CollectionType.DefaultUserCollection
|
||||
},
|
||||
groups: null,
|
||||
[new CollectionAccessSelection
|
||||
{
|
||||
Id = request.OrganizationUser!.Id,
|
||||
Manage = true
|
||||
}]);
|
||||
await collectionRepository.CreateDefaultCollectionsAsync(
|
||||
request.Organization!.Id,
|
||||
[request.OrganizationUser!.Id],
|
||||
request.DefaultUserCollectionName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -294,21 +293,10 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultCollection = new Collection
|
||||
{
|
||||
OrganizationId = organizationUser.OrganizationId,
|
||||
Name = defaultUserCollectionName,
|
||||
Type = CollectionType.DefaultUserCollection
|
||||
};
|
||||
var collectionUser = new CollectionAccessSelection
|
||||
{
|
||||
Id = organizationUser.Id,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true
|
||||
};
|
||||
|
||||
await _collectionRepository.CreateAsync(defaultCollection, groups: null, users: [collectionUser]);
|
||||
await _collectionRepository.CreateDefaultCollectionsAsync(
|
||||
organizationUser.OrganizationId,
|
||||
[organizationUser.Id],
|
||||
defaultUserCollectionName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -339,7 +327,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
return;
|
||||
}
|
||||
|
||||
await _collectionRepository.UpsertDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName);
|
||||
await _collectionRepository.CreateDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -57,14 +57,15 @@ public class OrganizationDataOwnershipPolicyValidator(
|
||||
var userOrgIds = requirements
|
||||
.Select(requirement => requirement.GetDefaultCollectionRequestOnPolicyEnable(policyUpdate.OrganizationId))
|
||||
.Where(request => request.ShouldCreateDefaultCollection)
|
||||
.Select(request => request.OrganizationUserId);
|
||||
.Select(request => request.OrganizationUserId)
|
||||
.ToList();
|
||||
|
||||
if (!userOrgIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await collectionRepository.UpsertDefaultCollectionsAsync(
|
||||
await collectionRepository.CreateDefaultCollectionsBulkAsync(
|
||||
policyUpdate.OrganizationId,
|
||||
userOrgIds,
|
||||
defaultCollectionName);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Extensions;
|
||||
@@ -51,7 +52,7 @@ public static class InvoiceExtensions
|
||||
if (string.IsNullOrEmpty(priceInfo) && line.Quantity > 0)
|
||||
{
|
||||
var pricePerItem = (line.Amount / 100m) / line.Quantity;
|
||||
priceInfo = $"(at ${pricePerItem:F2} / month)";
|
||||
priceInfo = string.Format(CultureInfo.InvariantCulture, "(at ${0:F2} / month)", pricePerItem);
|
||||
}
|
||||
|
||||
var taxDescription = $"{line.Quantity} × Tax {priceInfo}";
|
||||
@@ -70,7 +71,7 @@ public static class InvoiceExtensions
|
||||
if (tax > 0)
|
||||
{
|
||||
var taxAmount = tax / 100m;
|
||||
items.Add($"1 × Tax (at ${taxAmount:F2} / month)");
|
||||
items.Add(string.Format(CultureInfo.InvariantCulture, "1 × Tax (at ${0:F2} / month)", taxAmount));
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
@@ -164,6 +164,7 @@ public static class FeatureFlagKeys
|
||||
public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow";
|
||||
public const string RedirectOnSsoRequired = "pm-1632-redirect-on-sso-required";
|
||||
public const string PrefetchPasswordPrelogin = "pm-23801-prefetch-password-prelogin";
|
||||
public const string PM27086_UpdateAuthenticationApisForInputPassword = "pm-27086-update-authentication-apis-for-input-password";
|
||||
|
||||
/* Autofill Team */
|
||||
public const string SSHAgent = "ssh-agent";
|
||||
|
||||
@@ -64,11 +64,22 @@ public interface ICollectionRepository : IRepository<Collection, Guid>
|
||||
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups);
|
||||
|
||||
/// <summary>
|
||||
/// Creates default user collections for the specified organization users if they do not already have one.
|
||||
/// Creates default user collections for the specified organization users.
|
||||
/// Filters internally to only create collections for users who don't already have one.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The Organization ID.</param>
|
||||
/// <param name="organizationUserIds">The Organization User IDs to create default collections for.</param>
|
||||
/// <param name="defaultCollectionName">The encrypted string to use as the default collection name.</param>
|
||||
/// <returns></returns>
|
||||
Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName);
|
||||
Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName);
|
||||
|
||||
/// <summary>
|
||||
/// Creates default user collections for the specified organization users using bulk insert operations.
|
||||
/// Use this if you need to create collections for > ~1k users.
|
||||
/// Filters internally to only create collections for users who don't already have one.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The Organization ID.</param>
|
||||
/// <param name="organizationUserIds">The Organization User IDs to create default collections for.</param>
|
||||
/// <param name="defaultCollectionName">The encrypted string to use as the default collection name.</param>
|
||||
Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName);
|
||||
|
||||
}
|
||||
|
||||
@@ -160,6 +160,21 @@ public static class DapperHelpers
|
||||
return ids.ToArrayTVP("GuidId");
|
||||
}
|
||||
|
||||
public static DataTable ToTwoGuidIdArrayTVP(this IEnumerable<(Guid id1, Guid id2)> values)
|
||||
{
|
||||
var table = new DataTable();
|
||||
table.SetTypeName("[dbo].[TwoGuidIdArray]");
|
||||
table.Columns.Add("Id1", typeof(Guid));
|
||||
table.Columns.Add("Id2", typeof(Guid));
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
table.Rows.Add(value.id1, value.id2);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
public static DataTable ToArrayTVP<T>(this IEnumerable<T> values, string columnName)
|
||||
{
|
||||
var table = new DataTable();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Collections;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -360,7 +361,45 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
{
|
||||
organizationUserIds = organizationUserIds.ToList();
|
||||
if (!organizationUserIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var organizationUserCollectionIds = organizationUserIds
|
||||
.Select(ou => (ou, CoreHelpers.GenerateComb()))
|
||||
.ToTwoGuidIdArrayTVP();
|
||||
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
await connection.OpenAsync();
|
||||
await using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"[dbo].[Collection_CreateDefaultCollections]",
|
||||
new
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
DefaultCollectionName = defaultCollectionName,
|
||||
OrganizationUserCollectionIds = organizationUserCollectionIds
|
||||
},
|
||||
commandType: CommandType.StoredProcedure,
|
||||
transaction: transaction);
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
{
|
||||
organizationUserIds = organizationUserIds.ToList();
|
||||
if (!organizationUserIds.Any())
|
||||
@@ -377,7 +416,8 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
|
||||
var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection);
|
||||
|
||||
var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
var (collections, collectionUsers) =
|
||||
CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
|
||||
if (!collectionUsers.Any() || !collections.Any())
|
||||
{
|
||||
@@ -387,11 +427,11 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
|
||||
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
|
||||
|
||||
transaction.Commit();
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -421,40 +461,6 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
return organizationUserIds.ToHashSet();
|
||||
}
|
||||
|
||||
private (List<CollectionUser> collectionUser, List<Collection> collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable<Guid> missingDefaultCollectionUserIds, string defaultCollectionName)
|
||||
{
|
||||
var collectionUsers = new List<CollectionUser>();
|
||||
var collections = new List<Collection>();
|
||||
|
||||
foreach (var orgUserId in missingDefaultCollectionUserIds)
|
||||
{
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
|
||||
collections.Add(new Collection
|
||||
{
|
||||
Id = collectionId,
|
||||
OrganizationId = organizationId,
|
||||
Name = defaultCollectionName,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultUserCollectionEmail = null
|
||||
|
||||
});
|
||||
|
||||
collectionUsers.Add(new CollectionUser
|
||||
{
|
||||
CollectionId = collectionId,
|
||||
OrganizationUserId = orgUserId,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true,
|
||||
});
|
||||
}
|
||||
|
||||
return (collectionUsers, collections);
|
||||
}
|
||||
|
||||
public class CollectionWithGroupsAndUsers : Collection
|
||||
{
|
||||
public CollectionWithGroupsAndUsers() { }
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Collections;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -794,7 +793,7 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
|
||||
// SaveChangesAsync is expected to be called outside this method
|
||||
}
|
||||
|
||||
public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
{
|
||||
organizationUserIds = organizationUserIds.ToList();
|
||||
if (!organizationUserIds.Any())
|
||||
@@ -808,15 +807,15 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
|
||||
var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(dbContext, organizationId);
|
||||
var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection);
|
||||
|
||||
var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
var (collections, collectionUsers) = CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
|
||||
if (!collectionUsers.Any() || !collections.Any())
|
||||
if (!collections.Any() || !collectionUsers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await dbContext.BulkCopyAsync(collections);
|
||||
await dbContext.BulkCopyAsync(collectionUsers);
|
||||
await dbContext.Collections.AddRangeAsync(Mapper.Map<IEnumerable<Collection>>(collections));
|
||||
await dbContext.CollectionUsers.AddRangeAsync(Mapper.Map<IEnumerable<CollectionUser>>(collectionUsers));
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
@@ -844,37 +843,7 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
|
||||
return results.ToHashSet();
|
||||
}
|
||||
|
||||
private (List<CollectionUser> collectionUser, List<Collection> collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable<Guid> missingDefaultCollectionUserIds, string defaultCollectionName)
|
||||
{
|
||||
var collectionUsers = new List<CollectionUser>();
|
||||
var collections = new List<Collection>();
|
||||
|
||||
foreach (var orgUserId in missingDefaultCollectionUserIds)
|
||||
{
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
|
||||
collections.Add(new Collection
|
||||
{
|
||||
Id = collectionId,
|
||||
OrganizationId = organizationId,
|
||||
Name = defaultCollectionName,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultUserCollectionEmail = null
|
||||
|
||||
});
|
||||
|
||||
collectionUsers.Add(new CollectionUser
|
||||
{
|
||||
CollectionId = collectionId,
|
||||
OrganizationUserId = orgUserId,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true,
|
||||
});
|
||||
}
|
||||
|
||||
return (collectionUsers, collections);
|
||||
}
|
||||
public Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds,
|
||||
string defaultCollectionName) =>
|
||||
CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using DP = Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Repositories;
|
||||
|
||||
public class DatabaseContext : DbContext
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
-- Creates default user collections for organization users
|
||||
-- Filters out existing default collections at database level
|
||||
CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections]
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@DefaultCollectionName VARCHAR(MAX),
|
||||
@OrganizationUserCollectionIds AS [dbo].[TwoGuidIdArray] READONLY -- OrganizationUserId, CollectionId
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DECLARE @Now DATETIME2(7) = GETUTCDATE()
|
||||
|
||||
-- Filter to only users who don't have default collections
|
||||
SELECT ids.Id1, ids.Id2
|
||||
INTO #FilteredIds
|
||||
FROM @OrganizationUserCollectionIds ids
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM [dbo].[CollectionUser] cu
|
||||
INNER JOIN [dbo].[Collection] c ON c.Id = cu.CollectionId
|
||||
WHERE c.OrganizationId = @OrganizationId
|
||||
AND c.[Type] = 1 -- CollectionType.DefaultUserCollection
|
||||
AND cu.OrganizationUserId = ids.Id1
|
||||
);
|
||||
|
||||
-- Insert collections only for users who don't have default collections yet
|
||||
INSERT INTO [dbo].[Collection]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[Name],
|
||||
[CreationDate],
|
||||
[RevisionDate],
|
||||
[Type],
|
||||
[ExternalId],
|
||||
[DefaultUserCollectionEmail]
|
||||
)
|
||||
SELECT
|
||||
ids.Id2, -- CollectionId
|
||||
@OrganizationId,
|
||||
@DefaultCollectionName,
|
||||
@Now,
|
||||
@Now,
|
||||
1, -- CollectionType.DefaultUserCollection
|
||||
NULL,
|
||||
NULL
|
||||
FROM
|
||||
#FilteredIds ids;
|
||||
|
||||
-- Insert collection user mappings
|
||||
INSERT INTO [dbo].[CollectionUser]
|
||||
(
|
||||
[CollectionId],
|
||||
[OrganizationUserId],
|
||||
[ReadOnly],
|
||||
[HidePasswords],
|
||||
[Manage]
|
||||
)
|
||||
SELECT
|
||||
ids.Id2, -- CollectionId
|
||||
ids.Id1, -- OrganizationUserId
|
||||
0, -- ReadOnly = false
|
||||
0, -- HidePasswords = false
|
||||
1 -- Manage = true
|
||||
FROM
|
||||
#FilteredIds ids;
|
||||
|
||||
DROP TABLE #FilteredIds;
|
||||
END
|
||||
Reference in New Issue
Block a user