// FIXME: Update this file to be null safe and then delete the line below #nullable disable using System.Security.Claims; 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.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Http; namespace Bit.Core.Context; public class CurrentContext( IProviderOrganizationRepository _providerOrganizationRepository, IProviderUserRepository _providerUserRepository) : ICurrentContext { private bool _builtHttpContext; private bool _builtClaimsPrincipal; private IEnumerable _providerOrganizationProviderDetails; private IEnumerable _providerUserOrganizations; public virtual HttpContext HttpContext { get; set; } public virtual Guid? UserId { get; set; } public virtual User User { get; set; } public virtual string DeviceIdentifier { get; set; } public virtual DeviceType? DeviceType { get; set; } public virtual string IpAddress { get; set; } public virtual string CountryName { get; set; } public virtual List Organizations { get; set; } public virtual List Providers { get; set; } public virtual Guid? InstallationId { get; set; } public virtual Guid? OrganizationId { get; set; } public virtual string ClientId { get; set; } public virtual Version ClientVersion { get; set; } public virtual bool ClientVersionIsPrerelease { get; set; } public virtual IdentityClientType IdentityClientType { get; set; } public virtual Guid? ServiceAccountOrganizationId { get; set; } public async virtual Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings) { if (_builtHttpContext) { return; } _builtHttpContext = true; HttpContext = httpContext; await BuildAsync(httpContext.User, globalSettings); if (DeviceIdentifier == null && httpContext.Request.Headers.TryGetValue("Device-Identifier", out var deviceIdentifier)) { DeviceIdentifier = deviceIdentifier; } if (httpContext.Request.Headers.TryGetValue("Device-Type", out var deviceType) && Enum.TryParse(deviceType.ToString(), out DeviceType dType)) { DeviceType = dType; } if (httpContext.Request.Headers.TryGetValue("Bitwarden-Client-Version", out var bitWardenClientVersion) && Version.TryParse(bitWardenClientVersion, out var cVersion)) { ClientVersion = cVersion; } if (httpContext.Request.Headers.TryGetValue("Is-Prerelease", out var clientVersionIsPrerelease)) { ClientVersionIsPrerelease = clientVersionIsPrerelease == "1"; } if (httpContext.Request.Headers.TryGetValue("country-name", out var countryName)) { CountryName = countryName; } } public async virtual Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings) { if (_builtClaimsPrincipal) { return; } _builtClaimsPrincipal = true; IpAddress = HttpContext.GetIpAddress(globalSettings); await SetContextAsync(user); } public virtual Task SetContextAsync(ClaimsPrincipal user) { if (user == null || !user.Claims.Any()) { return Task.FromResult(0); } var claimsDict = user.Claims.GroupBy(c => c.Type).ToDictionary(c => c.Key, c => c.Select(v => v)); ClientId = GetClaimValue(claimsDict, "client_id"); var clientType = GetClaimValue(claimsDict, Claims.Type); if (clientType != null) { if (Enum.TryParse(clientType, out IdentityClientType c)) { IdentityClientType = c; } } if (IdentityClientType == IdentityClientType.Send) { // For the Send client, we don't need to set any User specific properties on the context // so just short circuit and return here. return Task.FromResult(0); } var subject = GetClaimValue(claimsDict, "sub"); if (Guid.TryParse(subject, out var subIdGuid)) { UserId = subIdGuid; } ClientId = GetClaimValue(claimsDict, "client_id"); var clientSubject = GetClaimValue(claimsDict, "client_sub"); var orgApi = false; if (clientSubject != null) { if (ClientId?.StartsWith("installation.") ?? false) { if (Guid.TryParse(clientSubject, out var idGuid)) { InstallationId = idGuid; } } else if (ClientId?.StartsWith("organization.") ?? false) { if (Guid.TryParse(clientSubject, out var idGuid)) { OrganizationId = idGuid; orgApi = true; } } } if (IdentityClientType == IdentityClientType.ServiceAccount) { ServiceAccountOrganizationId = new Guid(GetClaimValue(claimsDict, Claims.Organization)); } DeviceIdentifier = GetClaimValue(claimsDict, Claims.Device); if (Enum.TryParse(GetClaimValue(claimsDict, Claims.DeviceType), out DeviceType deviceType)) { DeviceType = deviceType; } Organizations = GetOrganizations(claimsDict, orgApi); Providers = GetProviders(claimsDict); return Task.FromResult(0); } private List GetOrganizations(Dictionary> claimsDict, bool orgApi) { var accessSecretsManager = claimsDict.TryGetValue(Claims.SecretsManagerAccess, out var secretsManagerAccessClaim) ? secretsManagerAccessClaim.ToDictionary(s => s.Value, _ => true) : new Dictionary(); var organizations = new List(); if (claimsDict.TryGetValue(Claims.OrganizationOwner, out var organizationOwnerClaim)) { organizations.AddRange(organizationOwnerClaim.Select(c => new CurrentContextOrganization { Id = new Guid(c.Value), Type = OrganizationUserType.Owner, AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value), })); } else if (orgApi && OrganizationId.HasValue) { organizations.Add(new CurrentContextOrganization { Id = OrganizationId.Value, Type = OrganizationUserType.Owner, }); } if (claimsDict.TryGetValue(Claims.OrganizationAdmin, out var organizationAdminClaim)) { organizations.AddRange(organizationAdminClaim.Select(c => new CurrentContextOrganization { Id = new Guid(c.Value), Type = OrganizationUserType.Admin, AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value), })); } if (claimsDict.TryGetValue(Claims.OrganizationUser, out var organizationUserClaim)) { organizations.AddRange(organizationUserClaim.Select(c => new CurrentContextOrganization { Id = new Guid(c.Value), Type = OrganizationUserType.User, AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value), })); } if (claimsDict.TryGetValue(Claims.OrganizationCustom, out var organizationCustomClaim)) { organizations.AddRange(organizationCustomClaim.Select(c => new CurrentContextOrganization { Id = new Guid(c.Value), Type = OrganizationUserType.Custom, Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict), AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value), })); } return organizations; } private List GetProviders(Dictionary> claimsDict) { var providers = new List(); if (claimsDict.TryGetValue(Claims.ProviderAdmin, out var providerAdminClaim)) { providers.AddRange(providerAdminClaim.Select(c => new CurrentContextProvider { Id = new Guid(c.Value), Type = ProviderUserType.ProviderAdmin })); } if (claimsDict.TryGetValue(Claims.ProviderServiceUser, out var providerServiceUserClaim)) { providers.AddRange(providerServiceUserClaim.Select(c => new CurrentContextProvider { Id = new Guid(c.Value), Type = ProviderUserType.ServiceUser })); } return providers; } public async Task OrganizationUser(Guid orgId) { return (Organizations?.Any(o => o.Id == orgId) ?? false) || await OrganizationOwner(orgId); } public async Task OrganizationAdmin(Guid orgId) { return await OrganizationOwner(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Admin) ?? false); } public async Task OrganizationOwner(Guid orgId) { if (Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner) ?? false) { return true; } if (Providers.Any()) { return await ProviderUserForOrgAsync(orgId); } return false; } public Task OrganizationCustom(Guid orgId) { return Task.FromResult(Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Custom) ?? false); } public async Task AccessEventLogs(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.AccessEventLogs ?? false)) ?? false); } public async Task AccessImportExport(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.AccessImportExport ?? false)) ?? false); } public async Task AccessReports(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.AccessReports ?? false)) ?? false); } public async Task EditAnyCollection(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.EditAnyCollection ?? false)) ?? false); } public async Task ViewAllCollections(Guid orgId) { var org = GetOrganization(orgId); return await EditAnyCollection(orgId) || (org != null && org.Permissions.DeleteAnyCollection); } public async Task ManageGroups(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManageGroups ?? false)) ?? false); } public async Task ManagePolicies(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManagePolicies ?? false)) ?? false); } public async Task ManageSso(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManageSso ?? false)) ?? false); } public async Task ManageScim(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManageScim ?? false)) ?? false); } public async Task ManageUsers(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManageUsers ?? false)) ?? false); } public async Task ManageResetPassword(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && (o.Permissions?.ManageResetPassword ?? false)) ?? false); } public async Task ViewSubscription(Guid orgId) { var isManagedByBillableProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId && po.ProviderType.SupportsConsolidatedBilling()); return isManagedByBillableProvider ? await ProviderUserForOrgAsync(orgId) : await OrganizationOwner(orgId); } public async Task EditSubscription(Guid orgId) { var orgManagedByProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId); return orgManagedByProvider ? await ProviderUserForOrgAsync(orgId) : await OrganizationOwner(orgId); } public async Task EditPaymentMethods(Guid orgId) { return await EditSubscription(orgId); } public async Task ViewBillingHistory(Guid orgId) { return await EditSubscription(orgId); } public async Task AccessMembersTab(Guid orgId) { return await OrganizationAdmin(orgId) || await ManageUsers(orgId) || await ManageResetPassword(orgId); } public bool ProviderProviderAdmin(Guid providerId) { return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false; } public bool ProviderManageUsers(Guid providerId) { return ProviderProviderAdmin(providerId); } public bool ProviderAccessEventLogs(Guid providerId) { return ProviderProviderAdmin(providerId); } public bool AccessProviderOrganizations(Guid providerId) { return ProviderUser(providerId); } public bool ManageProviderOrganizations(Guid providerId) { return ProviderProviderAdmin(providerId); } public bool ProviderUser(Guid providerId) { return Providers?.Any(o => o.Id == providerId) ?? false; } public async Task ProviderUserForOrgAsync(Guid orgId) { return (await GetProviderUserOrganizations()).Any(po => po.OrganizationId == orgId); } public async Task ProviderIdForOrg(Guid orgId) { if (Organizations?.Any(org => org.Id == orgId) ?? false) { return null; } var po = (await GetProviderUserOrganizations()) ?.FirstOrDefault(po => po.OrganizationId == orgId); return po?.ProviderId; } public bool AccessSecretsManager(Guid orgId) { if (ServiceAccountOrganizationId.HasValue && ServiceAccountOrganizationId.Value == orgId) { return true; } return Organizations?.Any(o => o.Id == orgId && o.AccessSecretsManager) ?? false; } public async Task> OrganizationMembershipAsync( IOrganizationUserRepository organizationUserRepository, Guid userId) { if (Organizations == null) { // If we haven't had our user id set, take the one passed in since we are about to get information // for them anyways. UserId ??= userId; var userOrgs = await organizationUserRepository.GetManyDetailsByUserAsync(userId); Organizations = userOrgs.Where(ou => ou.Status == OrganizationUserStatusType.Confirmed) .Select(ou => new CurrentContextOrganization(ou)).ToList(); } return Organizations; } public async Task> ProviderMembershipAsync( IProviderUserRepository providerUserRepository, Guid userId) { if (Providers == null) { // If we haven't had our user id set, take the one passed in since we are about to get information // for them anyways. UserId ??= userId; var userProviders = await providerUserRepository.GetManyByUserAsync(userId); Providers = userProviders.Where(ou => ou.Status == ProviderUserStatusType.Confirmed) .Select(ou => new CurrentContextProvider(ou)).ToList(); } return Providers; } public CurrentContextOrganization GetOrganization(Guid orgId) { return Organizations?.Find(o => o.Id == orgId); } private string GetClaimValue(Dictionary> claims, string type) { if (!claims.TryGetValue(type, out var claim)) { return null; } return claim.FirstOrDefault()?.Value; } private Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary> claimsDict) { bool hasClaim(string claimKey) { return claimsDict.TryGetValue(claimKey, out var claim) ? claim.Any(x => x.Value == organizationId) : false; } return new Permissions { AccessEventLogs = hasClaim("accesseventlogs"), AccessImportExport = hasClaim("accessimportexport"), AccessReports = hasClaim("accessreports"), CreateNewCollections = hasClaim("createnewcollections"), EditAnyCollection = hasClaim("editanycollection"), DeleteAnyCollection = hasClaim("deleteanycollection"), ManageGroups = hasClaim("managegroups"), ManagePolicies = hasClaim("managepolicies"), ManageSso = hasClaim("managesso"), ManageUsers = hasClaim("manageusers"), ManageResetPassword = hasClaim("manageresetpassword"), ManageScim = hasClaim("managescim"), }; } protected async Task> GetProviderUserOrganizations() { if (_providerUserOrganizations == null && UserId.HasValue) { _providerUserOrganizations = await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(UserId.Value, ProviderUserStatusType.Confirmed); } return _providerUserOrganizations; } protected async Task> GetOrganizationProviderDetails() { if (_providerOrganizationProviderDetails == null && UserId.HasValue) { _providerOrganizationProviderDetails = await _providerOrganizationRepository.GetManyByUserAsync(UserId.Value); } return _providerOrganizationProviderDetails; } }