mirror of
https://github.com/bitwarden/server
synced 2025-12-19 01:33:20 +00:00
Merge branch 'master' into feature/sm-billing
This commit is contained in:
@@ -293,6 +293,16 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
|
|||||||
return policy == null ? (false, false) : (policy.Read, policy.Write);
|
return policy == null ? (false, false) : (policy.Read, policy.Write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task EmptyTrash(DateTime currentDate, uint deleteAfterThisNumberOfDays)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
|
||||||
|
await dbContext.Secret.Where(s => s.DeletedDate != null && s.DeletedDate < currentDate.AddDays(-deleteAfterThisNumberOfDays)).ExecuteDeleteAsync();
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private IQueryable<SecretPermissionDetails> SecretToPermissionDetails(IQueryable<Secret> query, Guid userId, AccessClientType accessType)
|
private IQueryable<SecretPermissionDetails> SecretToPermissionDetails(IQueryable<Secret> query, Guid userId, AccessClientType accessType)
|
||||||
{
|
{
|
||||||
var secrets = accessType switch
|
var secrets = accessType switch
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
.StartNow()
|
.StartNow()
|
||||||
.WithCronSchedule("0 30 */12 * * ?")
|
.WithCronSchedule("0 30 */12 * * ?")
|
||||||
.Build();
|
.Build();
|
||||||
|
var smTrashCleanupTrigger = TriggerBuilder.Create()
|
||||||
|
.WithIdentity("SMTrashCleanupTrigger")
|
||||||
|
.StartNow()
|
||||||
|
.WithCronSchedule("0 0 22 * * ?")
|
||||||
|
.Build();
|
||||||
var randomDailySponsorshipSyncTrigger = TriggerBuilder.Create()
|
var randomDailySponsorshipSyncTrigger = TriggerBuilder.Create()
|
||||||
.WithIdentity("RandomDailySponsorshipSyncTrigger")
|
.WithIdentity("RandomDailySponsorshipSyncTrigger")
|
||||||
.StartAt(DateBuilder.FutureDate(new Random().Next(24), IntervalUnit.Hour))
|
.StartAt(DateBuilder.FutureDate(new Random().Next(24), IntervalUnit.Hour))
|
||||||
@@ -70,6 +75,10 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
jobs.Add(new Tuple<Type, ITrigger>(typeof(SelfHostedSponsorshipSyncJob), randomDailySponsorshipSyncTrigger));
|
jobs.Add(new Tuple<Type, ITrigger>(typeof(SelfHostedSponsorshipSyncJob), randomDailySponsorshipSyncTrigger));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !OSS
|
||||||
|
jobs.Add(new Tuple<Type, ITrigger>(typeof(EmptySecretsManagerTrashJob), smTrashCleanupTrigger));
|
||||||
|
#endif
|
||||||
|
|
||||||
Jobs = jobs;
|
Jobs = jobs;
|
||||||
|
|
||||||
await base.StartAsync(cancellationToken);
|
await base.StartAsync(cancellationToken);
|
||||||
@@ -88,4 +97,9 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
services.AddTransient<ValidateOrganizationsJob>();
|
services.AddTransient<ValidateOrganizationsJob>();
|
||||||
services.AddTransient<ValidateOrganizationDomainJob>();
|
services.AddTransient<ValidateOrganizationDomainJob>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddCommercialSecretsManagerJobServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddTransient<EmptySecretsManagerTrashJob>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/Api/SecretsManager/Jobs/EmptySecretsManagerTrashJob.cs
Normal file
23
src/Api/SecretsManager/Jobs/EmptySecretsManagerTrashJob.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Bit.Core.Jobs;
|
||||||
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace Bit.Api.Jobs;
|
||||||
|
|
||||||
|
public class EmptySecretsManagerTrashJob : BaseJob
|
||||||
|
{
|
||||||
|
private ISecretRepository _secretRepository;
|
||||||
|
private const uint DeleteAfterThisNumberOfDays = 30;
|
||||||
|
|
||||||
|
public EmptySecretsManagerTrashJob(ISecretRepository secretRepository, ILogger<EmptySecretsManagerTrashJob> logger) : base(logger)
|
||||||
|
{
|
||||||
|
_secretRepository = secretRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Execute job task: EmptySecretsManagerTrashJob: Start");
|
||||||
|
await _secretRepository.EmptyTrash(DateTime.UtcNow, DeleteAfterThisNumberOfDays);
|
||||||
|
_logger.LogInformation("Execute job task: EmptySecretsManagerTrashJob: End");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -149,6 +149,7 @@ public class Startup
|
|||||||
services.AddCommercialCoreServices();
|
services.AddCommercialCoreServices();
|
||||||
services.AddCommercialSecretsManagerServices();
|
services.AddCommercialSecretsManagerServices();
|
||||||
services.AddSecretsManagerEfRepositories();
|
services.AddSecretsManagerEfRepositories();
|
||||||
|
Jobs.JobsHostedService.AddCommercialSecretsManagerJobServices(services);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MVC
|
// MVC
|
||||||
|
|||||||
@@ -63,9 +63,16 @@ public class SsoConfigService : ISsoConfigService
|
|||||||
throw new BadRequestException("Key Connector cannot be disabled at this moment.");
|
throw new BadRequestException("Key Connector cannot be disabled at this moment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically enable reset password policy if trusted device encryption is selected
|
// Automatically enable account recovery and single org policies if trusted device encryption is selected
|
||||||
if (config.GetData().MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption)
|
if (config.GetData().MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption)
|
||||||
{
|
{
|
||||||
|
var singleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg) ??
|
||||||
|
new Policy { OrganizationId = config.OrganizationId, Type = PolicyType.SingleOrg };
|
||||||
|
|
||||||
|
singleOrgPolicy.Enabled = true;
|
||||||
|
|
||||||
|
await _policyService.SaveAsync(singleOrgPolicy, _userService, _organizationService, null);
|
||||||
|
|
||||||
var resetPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.ResetPassword) ??
|
var resetPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.ResetPassword) ??
|
||||||
new Policy { OrganizationId = config.OrganizationId, Type = PolicyType.ResetPassword, };
|
new Policy { OrganizationId = config.OrganizationId, Type = PolicyType.ResetPassword, };
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ public interface ISecretRepository
|
|||||||
Task<IEnumerable<Secret>> ImportAsync(IEnumerable<Secret> secrets);
|
Task<IEnumerable<Secret>> ImportAsync(IEnumerable<Secret> secrets);
|
||||||
Task UpdateRevisionDates(IEnumerable<Guid> ids);
|
Task UpdateRevisionDates(IEnumerable<Guid> ids);
|
||||||
Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType);
|
Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType);
|
||||||
|
Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public class PolicyService : IPolicyService
|
|||||||
await RequiredBySsoAsync(org);
|
await RequiredBySsoAsync(org);
|
||||||
await RequiredByVaultTimeoutAsync(org);
|
await RequiredByVaultTimeoutAsync(org);
|
||||||
await RequiredByKeyConnectorAsync(org);
|
await RequiredByKeyConnectorAsync(org);
|
||||||
|
await RequiredByAccountRecoveryAsync(org);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -80,6 +81,11 @@ public class PolicyService : IPolicyService
|
|||||||
{
|
{
|
||||||
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (policy.Enabled)
|
||||||
|
{
|
||||||
|
await DependsOnSingleOrgAsync(org);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PolicyType.MaximumVaultTimeout:
|
case PolicyType.MaximumVaultTimeout:
|
||||||
@@ -244,6 +250,15 @@ public class PolicyService : IPolicyService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RequiredByAccountRecoveryAsync(Organization org)
|
||||||
|
{
|
||||||
|
var requireSso = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.ResetPassword);
|
||||||
|
if (requireSso?.Enabled == true)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Account recovery policy is enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RequiredByVaultTimeoutAsync(Organization org)
|
private async Task RequiredByVaultTimeoutAsync(Organization org)
|
||||||
{
|
{
|
||||||
var vaultTimeout = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.MaximumVaultTimeout);
|
var vaultTimeout = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.MaximumVaultTimeout);
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ BEGIN
|
|||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
ar.*, ou.[Email], ou.[Id] AS [OrganizationUserId]
|
ar.*, u.[Email], ou.[Id] AS [OrganizationUserId]
|
||||||
FROM
|
FROM
|
||||||
[dbo].[AuthRequestView] ar
|
[dbo].[AuthRequestView] ar
|
||||||
INNER JOIN
|
INNER JOIN
|
||||||
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[User] u ON u.[Id] = ar.[UserId]
|
||||||
WHERE
|
WHERE
|
||||||
ar.[OrganizationId] = @OrganizationId
|
ar.[OrganizationId] = @OrganizationId
|
||||||
AND
|
AND
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ BEGIN
|
|||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
ar.*, ou.[Email], ou.[OrganizationId], ou.[Id] AS [OrganizationUserId]
|
ar.*, u.[Email], ou.[Id] AS [OrganizationUserId]
|
||||||
FROM
|
FROM
|
||||||
[dbo].[AuthRequestView] ar
|
[dbo].[AuthRequestView] ar
|
||||||
INNER JOIN
|
INNER JOIN
|
||||||
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[User] u ON u.[Id] = ar.[UserId]
|
||||||
WHERE
|
WHERE
|
||||||
ar.[OrganizationId] = @OrganizationId
|
ar.[OrganizationId] = @OrganizationId
|
||||||
AND
|
AND
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ using Bit.Core.Auth.Services;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
|
using Bit.Core.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@@ -317,4 +319,40 @@ public class SsoConfigServiceTests
|
|||||||
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||||
.UpsertAsync(default);
|
.UpsertAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_Tde_Enable_Required_Policies(SutProvider<SsoConfigService> sutProvider, Organization organization)
|
||||||
|
{
|
||||||
|
var ssoConfig = new SsoConfig
|
||||||
|
{
|
||||||
|
Id = default,
|
||||||
|
Data = new SsoConfigurationData
|
||||||
|
{
|
||||||
|
MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption,
|
||||||
|
}.Serialize(),
|
||||||
|
Enabled = true,
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||||
|
.SaveAsync(
|
||||||
|
Arg.Is<Policy>(t => t.Type == Enums.PolicyType.SingleOrg),
|
||||||
|
Arg.Any<IUserService>(),
|
||||||
|
Arg.Any<IOrganizationService>(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||||
|
.SaveAsync(
|
||||||
|
Arg.Is<Policy>(t => t.Type == Enums.PolicyType.ResetPassword && t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled),
|
||||||
|
Arg.Any<IUserService>(),
|
||||||
|
Arg.Any<IOrganizationService>(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||||
|
.UpsertAsync(default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ public class PolicyServiceTests
|
|||||||
UsePolicies = true,
|
UsePolicies = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetByOrganizationIdTypeAsync(policy.OrganizationId, Enums.PolicyType.SingleOrg)
|
||||||
|
.Returns(Task.FromResult(new Policy { Enabled = true }));
|
||||||
|
|
||||||
var utcNow = DateTime.UtcNow;
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
await sutProvider.Sut.SaveAsync(policy, Substitute.For<IUserService>(), Substitute.For<IOrganizationService>(), Guid.NewGuid());
|
await sutProvider.Sut.SaveAsync(policy, Substitute.For<IUserService>(), Substitute.For<IOrganizationService>(), Guid.NewGuid());
|
||||||
@@ -444,6 +448,70 @@ public class PolicyServiceTests
|
|||||||
.LogPolicyEventAsync(default, default, default);
|
.LogPolicyEventAsync(default, default, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_PolicyRequiredForAccountRecovery_NotEnabled_ThrowsBadRequestAsync(
|
||||||
|
[PolicyFixtures.Policy(Enums.PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||||
|
{
|
||||||
|
policy.Enabled = true;
|
||||||
|
policy.SetDataModel(new ResetPasswordDataModel());
|
||||||
|
|
||||||
|
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||||
|
{
|
||||||
|
Id = policy.OrganizationId,
|
||||||
|
UsePolicies = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||||
|
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||||
|
|
||||||
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SaveAsync(policy,
|
||||||
|
Substitute.For<IUserService>(),
|
||||||
|
Substitute.For<IOrganizationService>(),
|
||||||
|
Guid.NewGuid()));
|
||||||
|
|
||||||
|
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertAsync(default);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.LogPolicyEventAsync(default, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_SingleOrg_AccountRecoveryEnabled_ThrowsBadRequest(
|
||||||
|
[PolicyFixtures.Policy(Enums.PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||||
|
{
|
||||||
|
policy.Enabled = false;
|
||||||
|
|
||||||
|
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||||
|
{
|
||||||
|
Id = policy.OrganizationId,
|
||||||
|
UsePolicies = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetByOrganizationIdTypeAsync(policy.OrganizationId, Enums.PolicyType.ResetPassword)
|
||||||
|
.Returns(new Policy { Enabled = true });
|
||||||
|
|
||||||
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SaveAsync(policy,
|
||||||
|
Substitute.For<IUserService>(),
|
||||||
|
Substitute.For<IOrganizationService>(),
|
||||||
|
Guid.NewGuid()));
|
||||||
|
|
||||||
|
Assert.Contains("Account recovery policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertAsync(default);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
|
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
CREATE OR ALTER PROCEDURE [dbo].[AuthRequest_ReadAdminApprovalsByIds]
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER,
|
||||||
|
@Ids AS [dbo].[GuidIdArray] READONLY
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
ar.*, u.[Email], ou.[Id] AS [OrganizationUserId]
|
||||||
|
FROM
|
||||||
|
[dbo].[AuthRequestView] ar
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[User] u ON u.[Id] = ar.[UserId]
|
||||||
|
WHERE
|
||||||
|
ar.[OrganizationId] = @OrganizationId
|
||||||
|
AND
|
||||||
|
ar.[Type] = 2 -- AdminApproval
|
||||||
|
AND
|
||||||
|
ar.[Id] IN (SELECT [Id] FROM @Ids)
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [dbo].[AuthRequest_ReadPendingByOrganizationId]
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
ar.*, u.[Email], ou.[Id] AS [OrganizationUserId]
|
||||||
|
FROM
|
||||||
|
[dbo].[AuthRequestView] ar
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] ou ON ou.[UserId] = ar.[UserId] AND ou.[OrganizationId] = ar.[OrganizationId]
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[User] u ON u.[Id] = ar.[UserId]
|
||||||
|
WHERE
|
||||||
|
ar.[OrganizationId] = @OrganizationId
|
||||||
|
AND
|
||||||
|
ar.[ResponseDate] IS NULL
|
||||||
|
AND
|
||||||
|
ar.[Type] = 2 -- AdminApproval
|
||||||
|
END
|
||||||
|
GO
|
||||||
Reference in New Issue
Block a user