1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-16 01:51:21 +01:00

Add disable send policy (#1130)

* Add Disable Send policy

* Test DisableSend policy

* PR Review

* Update tests for using CurrentContext

This required making an interface for CurrentContext and mocking out
the members used. The interface can be expanded as needed for tests.

I moved CurrentContext to a folder, which changes the namespace
and causes a lot of file touches, but most are just adding a reference

* Fix failing test

* Update exemption to include all exempt users

* Move all CurrentContext usages to ICurrentContext

* PR review. Match messaging with Web
This commit is contained in:
Matt Gibson 2021-02-04 12:54:21 -06:00 committed by GitHub
parent 19e7ce8519
commit edd4bc2623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 437 additions and 99 deletions

View File

@ -136,6 +136,7 @@ namespace Bit.Portal.Controllers
case PolicyType.PasswordGenerator:
case PolicyType.TwoFactorAuthentication:
case PolicyType.PersonalOwnership:
case PolicyType.DisableSend:
break;
case PolicyType.SingleOrg:

View File

@ -1,5 +1,5 @@
using System;
using Bit.Core;
using Bit.Core.Context;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

View File

@ -86,6 +86,7 @@ namespace Bit.Portal.Models
case PolicyType.TwoFactorAuthentication:
case PolicyType.RequireSso:
case PolicyType.PersonalOwnership:
case PolicyType.DisableSend:
break;
default:
throw new ArgumentOutOfRangeException();

View File

@ -47,6 +47,11 @@ namespace Bit.Portal.Models
DescriptionKey = "PersonalOwnershipDescription";
break;
case PolicyType.DisableSend:
NameKey = "DisableSend";
DescriptionKey = "DisableSendDescription";
break;
default:
throw new ArgumentOutOfRangeException();
}

View File

@ -1,4 +1,5 @@
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Utilities;
using Bit.Portal.Utilities;
using Microsoft.AspNetCore.Builder;
@ -40,7 +41,7 @@ namespace Bit.Portal
// Context
services.AddScoped<EnterprisePortalCurrentContext>();
services.AddScoped<CurrentContext>((serviceProvider) =>
services.AddScoped<ICurrentContext, CurrentContext>((serviceProvider) =>
serviceProvider.GetService<EnterprisePortalCurrentContext>());
// Identity

View File

@ -61,6 +61,17 @@
@i18nService.T("PersonalOwnershipExemption")
</div>
}
@if (Model.PolicyType == PolicyType.DisableSend)
{
<div class="callout callout-warning" role="alert">
<h3 class="callout-heading">
<i class="fa fa-warning" *ngIf="icon" aria-hidden="true"></i>
@i18nService.T("Warning")
</h3>
@i18nService.T("DisableSendExemption")
</div>
}
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>

View File

@ -1,5 +1,6 @@
using System;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Utilities;
using Bit.Sso.Utilities;
using IdentityServer4.Extensions;
@ -39,7 +40,7 @@ namespace Bit.Sso
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Mvc
services.AddControllersWithViews();

View File

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Jobs;
using Bit.Core.Repositories;
using Bit.Core.Services;

View File

@ -1,6 +1,7 @@
using System;
using System.Globalization;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Identity;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Builder;
@ -45,7 +46,7 @@ namespace Bit.Admin
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Identity
services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings);

View File

@ -8,6 +8,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using Bit.Api.Utilities;
using Bit.Core.Utilities;
using System.Collections.Generic;
@ -23,7 +24,7 @@ namespace Bit.Api.Controllers
private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public CiphersController(
@ -31,7 +32,7 @@ namespace Bit.Api.Controllers
ICollectionCipherRepository collectionCipherRepository,
ICipherService cipherService,
IUserService userService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_cipherRepository = cipherRepository;

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Table;
using System.Collections.Generic;
@ -20,13 +20,13 @@ namespace Bit.Api.Controllers
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public CollectionsController(
ICollectionRepository collectionRepository,
ICollectionService collectionService,
IUserService userService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_collectionRepository = collectionRepository;
_collectionService = collectionService;

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Data;
namespace Bit.Api.Controllers
@ -20,14 +20,14 @@ namespace Bit.Api.Controllers
private readonly ICipherRepository _cipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IEventRepository _eventRepository;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public EventsController(
IUserService userService,
ICipherRepository cipherRepository,
IOrganizationUserRepository organizationUserRepository,
IEventRepository eventRepository,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_userService = userService;
_cipherRepository = cipherRepository;

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using System.Collections.Generic;
namespace Bit.Api.Controllers
@ -18,12 +18,12 @@ namespace Bit.Api.Controllers
{
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_groupRepository = groupRepository;
_groupService = groupService;

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Security.Cryptography;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using System.Net;
using Bit.Core.Exceptions;
using System.Linq;
@ -22,7 +23,7 @@ namespace Bit.Api.Controllers
private static HttpClient _httpClient;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly string _userAgent;
@ -33,7 +34,7 @@ namespace Bit.Api.Controllers
public HibpController(
IUserService userService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_userService = userService;

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Bit.Core;
using Bit.Core.Context;
using System.Threading.Tasks;
using Bit.Core.Models.Business;
using Bit.Core.Exceptions;
@ -21,7 +21,7 @@ namespace Bit.Api.Controllers
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public LicensesController(
ILicensingService licensingService,
@ -29,7 +29,7 @@ namespace Bit.Api.Controllers
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_licensingService = licensingService;
_userRepository = userRepository;

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using System.Collections.Generic;
using Bit.Core.Models.Business;
@ -23,7 +23,7 @@ namespace Bit.Api.Controllers
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
@ -32,7 +32,7 @@ namespace Bit.Api.Controllers
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;

View File

@ -9,6 +9,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using Bit.Api.Utilities;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
@ -24,7 +25,7 @@ namespace Bit.Api.Controllers
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly IPaymentService _paymentService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IPolicyRepository _policyRepository;
@ -34,7 +35,7 @@ namespace Bit.Api.Controllers
IOrganizationService organizationService,
IUserService userService,
IPaymentService paymentService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
{

View File

@ -8,6 +8,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
@ -23,7 +24,7 @@ namespace Bit.Api.Controllers
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IDataProtector _organizationServiceDataProtector;
@ -33,7 +34,7 @@ namespace Bit.Api.Controllers
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository,
IUserService userService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IDataProtectionProvider dataProtectionProvider)
{

View File

@ -2,6 +2,7 @@
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api;
using System.Threading.Tasks;
@ -21,14 +22,14 @@ namespace Bit.Api.Controllers
private readonly IPushRegistrationService _pushRegistrationService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IWebHostEnvironment _environment;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public PushController(
IPushRegistrationService pushRegistrationService,
IPushNotificationService pushNotificationService,
IWebHostEnvironment environment,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_currentContext = currentContext;

View File

@ -10,6 +10,7 @@ using Bit.Core.Models.Table;
using Bit.Core.Enums;
using System.Linq;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Duo;
@ -25,7 +26,7 @@ namespace Bit.Api.Controllers
private readonly IOrganizationService _organizationService;
private readonly GlobalSettings _globalSettings;
private readonly UserManager<User> _userManager;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public TwoFactorController(
IUserService userService,
@ -33,7 +34,7 @@ namespace Bit.Api.Controllers
IOrganizationService organizationService,
GlobalSettings globalSettings,
UserManager<User> userManager,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_userService = userService;
_organizationRepository = organizationRepository;

View File

@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Api.Public;
using Bit.Core.Repositories;
using Bit.Core.Services;
@ -17,12 +17,12 @@ namespace Bit.Api.Public.Controllers
{
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionService _collectionService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public CollectionsController(
ICollectionRepository collectionRepository,
ICollectionService collectionService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_collectionRepository = collectionRepository;
_collectionService = collectionService;

View File

@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Api.Public;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
@ -17,12 +17,12 @@ namespace Bit.Api.Public.Controllers
{
private readonly IEventRepository _eventRepository;
private readonly ICipherRepository _cipherRepository;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public EventsController(
IEventRepository eventRepository,
ICipherRepository cipherRepository,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_eventRepository = eventRepository;
_cipherRepository = cipherRepository;

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Api.Public;
using Bit.Core.Repositories;
using Bit.Core.Services;
@ -18,12 +18,12 @@ namespace Bit.Api.Public.Controllers
{
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_groupRepository = groupRepository;
_groupService = groupService;

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Models.Api.Public;
using Bit.Core.Models.Business;
using Bit.Core.Repositories;
@ -21,14 +21,14 @@ namespace Bit.Api.Public.Controllers
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public MembersController(
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository,
IOrganizationService organizationService,
IUserService userService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_organizationUserRepository = organizationUserRepository;
_groupRepository = groupRepository;

View File

@ -2,6 +2,7 @@
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Public;
using Bit.Core.Services;
@ -15,12 +16,12 @@ namespace Bit.Api.Public.Controllers
public class OrganizationController : Controller
{
private readonly IOrganizationService _organizationService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public OrganizationController(
IOrganizationService organizationService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_organizationService = organizationService;

View File

@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Models.Api.Public;
using Bit.Core.Repositories;
@ -20,14 +20,14 @@ namespace Bit.Api.Public.Controllers
private readonly IPolicyService _policyService;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IUserService userService,
IOrganizationService organizationService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_policyRepository = policyRepository;
_policyService = policyService;

View File

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Bit.Api.Utilities;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Identity;
using Newtonsoft.Json.Serialization;
using AspNetCoreRateLimit;
@ -54,7 +55,7 @@ namespace Bit.Api
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Caching
services.AddMemoryCache();

View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Bit.Core;
using Bit.Core.Context;
using Stripe;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Http;
@ -46,7 +47,7 @@ namespace Bit.Billing
services.AddSingleton<BitPayClient>();
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Identity
services.AddCustomIdentityServices(globalSettings);

View File

@ -0,0 +1,24 @@
using System;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Bit.Core.Utilities;
namespace Bit.Core.Context
{
public class CurrentContentOrganization
{
public CurrentContentOrganization() { }
public CurrentContentOrganization(OrganizationUser orgUser)
{
Id = orgUser.OrganizationId;
Type = orgUser.Type;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions);
}
public Guid Id { get; set; }
public OrganizationUserType Type { get; set; }
public Permissions Permissions { get; set; }
}
}

View File

@ -10,9 +10,9 @@ using System.Security.Claims;
using Bit.Core.Utilities;
using Bit.Core.Models.Data;
namespace Bit.Core
namespace Bit.Core.Context
{
public class CurrentContext
public class CurrentContext : ICurrentContext
{
private bool _builtHttpContext;
private bool _builtClaimsPrincipal;
@ -296,21 +296,5 @@ namespace Bit.Core
ManageUsers = hasClaim("manageusers")
};
}
public class CurrentContentOrganization
{
public CurrentContentOrganization() { }
public CurrentContentOrganization(OrganizationUser orgUser)
{
Id = orgUser.OrganizationId;
Type = orgUser.Type;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions);
}
public Guid Id { get; set; }
public OrganizationUserType Type { get; set; }
public Permissions Permissions { get; set; }
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
using Microsoft.AspNetCore.Http;
namespace Bit.Core.Context
{
public interface ICurrentContext
{
HttpContext HttpContext { get; set; }
Guid? UserId { get; set; }
User User { get; set; }
string DeviceIdentifier { get; set; }
DeviceType? DeviceType { get; set; }
string IpAddress { get; set; }
List<CurrentContentOrganization> Organizations { get; set; }
Guid? InstallationId { get; set; }
Guid? OrganizationId { get; set; }
Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings);
Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings);
Task SetContextAsync(ClaimsPrincipal user);
bool OrganizationUser(Guid orgId);
bool OrganizationManager(Guid orgId);
bool OrganizationAdmin(Guid orgId);
bool OrganizationOwner(Guid orgId);
bool OrganizationCustom(Guid orgId);
bool AccessBusinessPortal(Guid orgId);
bool AccessEventLogs(Guid orgId);
bool AccessImportExport(Guid orgId);
bool AccessReports(Guid orgId);
bool ManageAllCollections(Guid orgId);
bool ManageAssignedCollections(Guid orgId);
bool ManageGroups(Guid orgId);
bool ManagePolicies(Guid orgId);
bool ManageSso(Guid orgId);
bool ManageUsers(Guid orgId);
Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync(
IOrganizationUserRepository organizationUserRepository, Guid userId);
}
}

View File

@ -8,5 +8,6 @@
SingleOrg = 3,
RequireSso = 4,
PersonalOwnership = 5,
DisableSend = 6,
}
}

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Identity;
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Context;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity
@ -18,12 +19,12 @@ namespace Bit.Core.Identity
{
private readonly IServiceProvider _serviceProvider;
private readonly IUserRepository _userRepository;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public UserStore(
IServiceProvider serviceProvider,
IUserRepository userRepository,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_serviceProvider = serviceProvider;
_userRepository = userRepository;

View File

@ -17,6 +17,7 @@ using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Bit.Core.Models.Api;
using Bit.Core.Context;
namespace Bit.Core.IdentityServer
{
@ -33,7 +34,7 @@ namespace Bit.Core.IdentityServer
private readonly IApplicationCacheService _applicationCacheService;
private readonly IMailService _mailService;
private readonly ILogger<ResourceOwnerPasswordValidator> _logger;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IPolicyRepository _policyRepository;
@ -49,7 +50,7 @@ namespace Bit.Core.IdentityServer
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
{

View File

@ -9,6 +9,7 @@ using IdentityModel;
using Bit.Core.Utilities;
using System.Security.Claims;
using Bit.Core.Services;
using Bit.Core.Context;
using System.Collections.ObjectModel;
namespace Bit.Core.IdentityServer
@ -21,7 +22,7 @@ namespace Bit.Core.IdentityServer
private readonly GlobalSettings _globalSettings;
private readonly StaticClientStore _staticClientStore;
private readonly ILicensingService _licensingService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationUserRepository _organizationUserRepository;
public ClientStore(
@ -31,7 +32,7 @@ namespace Bit.Core.IdentityServer
GlobalSettings globalSettings,
StaticClientStore staticClientStore,
ILicensingService licensingService,
CurrentContext currentContext,
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository)
{
_installationRepository = installationRepository;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Bit.Core.Services;
using Bit.Core.Context;
using System.Linq;
using Bit.Core.Identity;
using Microsoft.Extensions.Logging;
@ -31,7 +32,7 @@ namespace Bit.Core.IdentityServer
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,

View File

@ -9,6 +9,7 @@ using System.Linq;
using System;
using IdentityModel;
using Bit.Core.Utilities;
using Bit.Core.Context;
namespace Bit.Core.IdentityServer
{
@ -17,13 +18,13 @@ namespace Bit.Core.IdentityServer
private readonly IUserService _userService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ILicensingService _licensingService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
public ProfileService(
IUserService userService,
IOrganizationUserRepository organizationUserRepository,
ILicensingService licensingService,
CurrentContext currentContext)
ICurrentContext currentContext)
{
_userService = userService;
_organizationUserRepository = organizationUserRepository;

View File

@ -8,6 +8,7 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Bit.Core.Services;
using Bit.Core.Identity;
using Bit.Core.Context;
using Microsoft.Extensions.Logging;
namespace Bit.Core.IdentityServer
@ -30,7 +31,7 @@ namespace Bit.Core.IdentityServer
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,

View File

@ -1,5 +1,6 @@
using System;
using Bit.Core.Enums;
using Bit.Core.Context;
namespace Bit.Core.Models.Data
{
@ -7,7 +8,7 @@ namespace Bit.Core.Models.Data
{
public EventMessage() { }
public EventMessage(CurrentContext currentContext)
public EventMessage(ICurrentContext currentContext)
: base()
{
IpAddress = currentContext.IpAddress;

View File

@ -585,6 +585,17 @@
<value>Require users to save vault items to an organization by removing the personal ownership option.</value>
</data>
<data name="PersonalOwnershipExemption" xml:space="preserve">
<value>Organization users that can manage the organization's policies are exempt from this policy's enforcement.</value>
</data>
<data name="DisableSend" xml:space="preserve">
<value>Disable Send</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="DisableSendDescription" xml:space="preserve">
<value>Do not allow users to create or edit a Bitwarden Send. Deleting an existing Send is still allowed.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="DisableSendExemption" xml:space="preserve">
<value>Organization Owners and Administrators are exempt from this policy's enforcement.</value>
</data>
<data name="DisableRequireSsoError" xml:space="preserve">

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Models.Table;
using Bit.Core.Enums;
using Newtonsoft.Json;
@ -181,7 +182,7 @@ namespace Bit.Core.Services
}
var currentContext = _httpContextAccessor?.HttpContext?.
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier;
}

View File

@ -6,6 +6,7 @@ using Bit.Core.Models.Data;
using System.Linq;
using System.Collections.Generic;
using Bit.Core.Models.Table;
using Bit.Core.Context;
namespace Bit.Core.Services
{
@ -14,14 +15,14 @@ namespace Bit.Core.Services
private readonly IEventWriteService _eventWriteService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IApplicationCacheService _applicationCacheService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public EventService(
IEventWriteService eventWriteService,
IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_eventWriteService = eventWriteService;

View File

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using Microsoft.Azure.NotificationHubs;
using Bit.Core.Context;
using Bit.Core.Enums;
using Newtonsoft.Json;
using System.Collections.Generic;
@ -206,7 +207,7 @@ namespace Bit.Core.Services
}
var currentContext = _httpContextAccessor?.HttpContext?.
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Models.Table;
using Bit.Core.Enums;
using Newtonsoft.Json;
@ -187,7 +188,7 @@ namespace Bit.Core.Services
}
var currentContext = _httpContextAccessor?.HttpContext?.
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Models.Table;
using Bit.Core.Enums;
using Microsoft.AspNetCore.Http;
@ -198,7 +199,7 @@ namespace Bit.Core.Services
private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier)
{
var currentContext = _httpContextAccessor?.HttpContext?.
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
{
var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier);

View File

@ -1,6 +1,9 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
@ -14,12 +17,14 @@ namespace Bit.Core.Services
{
private readonly ISendRepository _sendRepository;
private readonly IUserRepository _userRepository;
private readonly IPolicyRepository _policyRepository;
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IPushNotificationService _pushService;
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
public SendService(
ISendRepository sendRepository,
@ -29,20 +34,27 @@ namespace Bit.Core.Services
ISendFileStorageService sendFileStorageService,
IPasswordHasher<User> passwordHasher,
IPushNotificationService pushService,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
ICurrentContext currentContext)
{
_sendRepository = sendRepository;
_userRepository = userRepository;
_userService = userService;
_policyRepository = policyRepository;
_organizationRepository = organizationRepository;
_sendFileStorageService = sendFileStorageService;
_passwordHasher = passwordHasher;
_pushService = pushService;
_globalSettings = globalSettings;
_currentContext = currentContext;
}
public async Task SaveSendAsync(Send send)
{
// Make sure user can save Sends
await ValidateUserCanSaveAsync(send.UserId);
if (send.Id == default(Guid))
{
await _sendRepository.CreateAsync(send);
@ -58,7 +70,7 @@ namespace Bit.Core.Services
public async Task CreateSendAsync(Send send, SendFileData data, Stream stream, long requestLength)
{
if (send.Type != Enums.SendType.File)
if (send.Type != SendType.File)
{
throw new BadRequestException("Send is not of type \"file\".");
}
@ -174,5 +186,28 @@ namespace Bit.Core.Services
{
return _passwordHasher.HashPassword(new User(), password);
}
private async Task ValidateUserCanSaveAsync(Guid? userId)
{
if (!userId.HasValue || (!_currentContext.Organizations?.Any() ?? true))
{
return;
}
var policies = await _policyRepository.GetManyByUserIdAsync(userId.Value);
if (policies == null)
{
return;
}
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.DisableSend))
{
if (!_currentContext.ManagePolicies(policy.OrganizationId))
{
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}
}
}
}
}

View File

@ -14,6 +14,7 @@ using Bit.Core.Models.Business;
using U2fLib = U2F.Core.Crypto.U2F;
using U2F.Core.Models;
using U2F.Core.Utils;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System.IO;
@ -46,7 +47,7 @@ namespace Bit.Core.Services
private readonly IPolicyRepository _policyRepository;
private readonly IDataProtector _organizationServiceDataProtector;
private readonly IReferenceEventService _referenceEventService;
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IOrganizationService _organizationService;
@ -74,7 +75,7 @@ namespace Bit.Core.Services
IPaymentService paymentService,
IPolicyRepository policyRepository,
IReferenceEventService referenceEventService,
CurrentContext currentContext,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IOrganizationService organizationService)
: base(

View File

@ -15,6 +15,7 @@ using System.Globalization;
using System.Web;
using Microsoft.AspNetCore.DataProtection;
using Bit.Core.Enums;
using Bit.Core.Context;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
@ -685,7 +686,7 @@ namespace Bit.Core.Utilities
return configDict;
}
public static List<KeyValuePair<string, string>> BuildIdentityClaims(User user, ICollection<CurrentContext.CurrentContentOrganization> orgs, bool isPremium)
public static List<KeyValuePair<string, string>> BuildIdentityClaims(User user, ICollection<CurrentContentOrganization> orgs, bool isPremium)
{
var claims = new List<KeyValuePair<string, string>>()
{

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Bit.Core.Context;
namespace Bit.Core.Utilities
{
@ -12,7 +13,7 @@ namespace Bit.Core.Utilities
_next = next;
}
public async Task Invoke(HttpContext httpContext, CurrentContext currentContext, GlobalSettings globalSettings)
public async Task Invoke(HttpContext httpContext, ICurrentContext currentContext, GlobalSettings globalSettings)
{
await currentContext.BuildAsync(httpContext, globalSettings);
await _next.Invoke(httpContext);

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
@ -18,12 +18,12 @@ namespace Bit.Events.Controllers
[Authorize("Application")]
public class CollectController : Controller
{
private readonly CurrentContext _currentContext;
private readonly ICurrentContext _currentContext;
private readonly IEventService _eventService;
private readonly ICipherRepository _cipherRepository;
public CollectController(
CurrentContext currentContext,
ICurrentContext currentContext,
IEventService eventService,
ICipherRepository cipherRepository)
{

View File

@ -1,5 +1,6 @@
using System.Globalization;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Services;
using Bit.Core.Utilities;
using IdentityModel;
@ -35,7 +36,7 @@ namespace Bit.Events
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Identity
services.AddIdentityAuthenticationServices(globalSettings, Environment, config =>

View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Utilities;
using AspNetCoreRateLimit;
using System.Globalization;
@ -49,7 +50,7 @@ namespace Bit.Identity
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<CurrentContext>();
services.AddScoped<ICurrentContext, CurrentContext>();
// Caching
services.AddMemoryCache();

View File

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Notifications

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using AutoFixture;
using AutoFixture.Kernel;
using Bit.Core.Context;
namespace Bit.Core.Test.AutoFixture.CurrentContextFixtures
{
internal class CurrentContext : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new CurrentContextBuilder());
}
}
internal class CurrentContextBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is Type typeRequest))
{
return new NoSpecimen();
}
if (typeof(ICurrentContext) != typeRequest)
{
return new NoSpecimen();
}
var obj = new Fixture().WithAutoNSubstitutions().Create<ICurrentContext>();
obj.Organizations = context.Create<List<CurrentContentOrganization>>();
return obj;
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using AutoFixture;
using Bit.Core.Models.Table;
using Bit.Core.Test.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.SendFixtures
{
internal class OrganizationSend : ICustomization
{
public Guid? OrganizationId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Send>(composer => composer
.With(s => s.OrganizationId, OrganizationId ?? Guid.NewGuid())
.Without(s => s.UserId));
}
}
internal class UserSend : ICustomization
{
public Guid? UserId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Send>(composer => composer
.With(s => s.UserId, UserId ?? Guid.NewGuid())
.Without(s => s.OrganizationId));
}
}
internal class UserSendAutoDataAttribute : CustomAutoDataAttribute
{
public UserSendAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
new UserSend { UserId = userId == null ? (Guid?)null : new Guid(userId) })
{ }
}
internal class InlineUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(UserSend) }, values)
{ }
}
internal class InlineKnownUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserSendAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new CurrentContextFixtures.CurrentContext(), new SutProviderCustomization(),
new UserSend { UserId = new Guid(userId) } }, values)
{ }
}
internal class OrganizationSendAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationSendAutoDataAttribute(string organizationId = null) : base(new CurrentContextFixtures.CurrentContext(),
new SutProviderCustomization(),
new OrganizationSend { OrganizationId = organizationId == null ? (Guid?)null : new Guid(organizationId) })
{ }
}
internal class InlineOrganizationSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(OrganizationSend) }, values)
{ }
}
}

View File

@ -16,10 +16,12 @@ namespace Bit.Core.Test.AutoFixture
public TSut Sut { get; private set; }
public Type SutType => typeof(TSut);
public SutProvider()
public SutProvider() : this(new Fixture()) { }
public SutProvider(IFixture fixture)
{
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
_fixture = new Fixture().WithAutoNSubstitutions();
_fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions();
_constructorParameterRelay = new ConstructorParameterRelay<TSut>(this, _fixture);
_fixture.Customizations.Add(_constructorParameterRelay);
}

View File

@ -6,6 +6,8 @@ namespace Bit.Core.Test.AutoFixture
{
public class SutProviderCustomization : ICustomization, ISpecimenBuilder
{
private IFixture _fixture = null;
public object Create(object request, ISpecimenContext context)
{
if (context == null)
@ -21,11 +23,12 @@ namespace Bit.Core.Test.AutoFixture
return new NoSpecimen();
}
return ((ISutProvider)Activator.CreateInstance(typeRequest)).Create();
return ((ISutProvider)Activator.CreateInstance(typeRequest, _fixture)).Create();
}
public void Customize(IFixture fixture)
{
_fixture = fixture;
fixture.Customizations.Add(this);
}
}

View File

@ -1,4 +1,5 @@
using System;
using Bit.Core.Context;
using Bit.Core.Repositories;
using Bit.Core.Services;
using NSubstitute;

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture;
using Bit.Core.Test.AutoFixture.SendFixtures;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services
{
public class SendServiceTests
{
private void SaveSendAsync_Setup(SendType sendType, bool canManagePolicies,
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
{
send.Id = default;
send.Type = sendType;
policies.First().Type = PolicyType.DisableSend;
policies.First().Enabled = true;
sutProvider.GetDependency<IPolicyRepository>().GetManyByUserIdAsync(send.UserId.Value).Returns(policies);
sutProvider.GetDependency<ICurrentContext>().ManagePolicies(Arg.Any<Guid>()).Returns(canManagePolicies);
}
[Theory]
[InlineUserSendAutoData(SendType.File)]
[InlineUserSendAutoData(SendType.Text)]
public async void SaveSendAsync_DisableSend_CantManagePolicies_throws(SendType sendType,
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
{
SaveSendAsync_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveSendAsync(send));
}
[Theory]
[InlineUserSendAutoData(SendType.File)]
[InlineUserSendAutoData(SendType.Text)]
public async void SaveSendAsync_DisableSend_DisabledPolicy_CantManagePolicies_success(SendType sendType,
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
{
SaveSendAsync_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
foreach (var policy in policies.Where(p => p.Type == PolicyType.DisableSend))
{
policy.Enabled = false;
}
await sutProvider.Sut.SaveSendAsync(send);
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
}
[Theory]
[InlineUserSendAutoData(SendType.File)]
[InlineUserSendAutoData(SendType.Text)]
public async void SaveSendAsync_DisableSend_CanManagePolicies_success(SendType sendType,
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
{
SaveSendAsync_Setup(sendType, canManagePolicies: true, sutProvider, send, policies);
await sutProvider.Sut.SaveSendAsync(send);
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
}
}
}

View File

@ -9,6 +9,7 @@ using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
using Bit.Core.Context;
namespace Bit.Core.Test.Services
{