1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 09:02:48 -05:00

Run formatting (#2230)

This commit is contained in:
Justin Baur
2022-08-29 16:06:55 -04:00
committed by GitHub
parent 9b7aef0763
commit 7f5f010e1e
1205 changed files with 73813 additions and 75022 deletions

View File

@ -1,78 +1,77 @@
using Bit.Core.Settings;
using IdentityServer4.Models;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ApiClient : Client
{
public class ApiClient : Client
public ApiClient(
GlobalSettings globalSettings,
string id,
int refreshTokenSlidingDays,
int accessTokenLifetimeHours,
string[] scopes = null)
{
public ApiClient(
GlobalSettings globalSettings,
string id,
int refreshTokenSlidingDays,
int accessTokenLifetimeHours,
string[] scopes = null)
ClientId = id;
AllowedGrantTypes = new[] { GrantType.ResourceOwnerPassword, GrantType.AuthorizationCode };
RefreshTokenExpiration = TokenExpiration.Sliding;
RefreshTokenUsage = TokenUsage.ReUse;
SlidingRefreshTokenLifetime = 86400 * refreshTokenSlidingDays;
AbsoluteRefreshTokenLifetime = 0; // forever
UpdateAccessTokenClaimsOnRefresh = true;
AccessTokenLifetime = 3600 * accessTokenLifetimeHours;
AllowOfflineAccess = true;
RequireConsent = false;
RequirePkce = true;
RequireClientSecret = false;
if (id == "web")
{
ClientId = id;
AllowedGrantTypes = new[] { GrantType.ResourceOwnerPassword, GrantType.AuthorizationCode };
RefreshTokenExpiration = TokenExpiration.Sliding;
RefreshTokenUsage = TokenUsage.ReUse;
SlidingRefreshTokenLifetime = 86400 * refreshTokenSlidingDays;
AbsoluteRefreshTokenLifetime = 0; // forever
UpdateAccessTokenClaimsOnRefresh = true;
AccessTokenLifetime = 3600 * accessTokenLifetimeHours;
AllowOfflineAccess = true;
RequireConsent = false;
RequirePkce = true;
RequireClientSecret = false;
if (id == "web")
{
RedirectUris = new[] { $"{globalSettings.BaseServiceUri.Vault}/sso-connector.html" };
PostLogoutRedirectUris = new[] { globalSettings.BaseServiceUri.Vault };
AllowedCorsOrigins = new[] { globalSettings.BaseServiceUri.Vault };
}
else if (id == "desktop")
{
RedirectUris = new[] { "bitwarden://sso-callback" };
PostLogoutRedirectUris = new[] { "bitwarden://logged-out" };
}
else if (id == "connector")
{
var connectorUris = new List<string>();
for (var port = 8065; port <= 8070; port++)
{
connectorUris.Add(string.Format("http://localhost:{0}", port));
}
RedirectUris = connectorUris.Append("bwdc://sso-callback").ToList();
PostLogoutRedirectUris = connectorUris.Append("bwdc://logged-out").ToList();
}
else if (id == "browser")
{
RedirectUris = new[] { $"{globalSettings.BaseServiceUri.Vault}/sso-connector.html" };
PostLogoutRedirectUris = new[] { globalSettings.BaseServiceUri.Vault };
AllowedCorsOrigins = new[] { globalSettings.BaseServiceUri.Vault };
}
else if (id == "cli")
{
var cliUris = new List<string>();
for (var port = 8065; port <= 8070; port++)
{
cliUris.Add(string.Format("http://localhost:{0}", port));
}
RedirectUris = cliUris;
PostLogoutRedirectUris = cliUris;
}
else if (id == "mobile")
{
RedirectUris = new[] { "bitwarden://sso-callback" };
PostLogoutRedirectUris = new[] { "bitwarden://logged-out" };
}
if (scopes == null)
{
scopes = new string[] { "api" };
}
AllowedScopes = scopes;
RedirectUris = new[] { $"{globalSettings.BaseServiceUri.Vault}/sso-connector.html" };
PostLogoutRedirectUris = new[] { globalSettings.BaseServiceUri.Vault };
AllowedCorsOrigins = new[] { globalSettings.BaseServiceUri.Vault };
}
else if (id == "desktop")
{
RedirectUris = new[] { "bitwarden://sso-callback" };
PostLogoutRedirectUris = new[] { "bitwarden://logged-out" };
}
else if (id == "connector")
{
var connectorUris = new List<string>();
for (var port = 8065; port <= 8070; port++)
{
connectorUris.Add(string.Format("http://localhost:{0}", port));
}
RedirectUris = connectorUris.Append("bwdc://sso-callback").ToList();
PostLogoutRedirectUris = connectorUris.Append("bwdc://logged-out").ToList();
}
else if (id == "browser")
{
RedirectUris = new[] { $"{globalSettings.BaseServiceUri.Vault}/sso-connector.html" };
PostLogoutRedirectUris = new[] { globalSettings.BaseServiceUri.Vault };
AllowedCorsOrigins = new[] { globalSettings.BaseServiceUri.Vault };
}
else if (id == "cli")
{
var cliUris = new List<string>();
for (var port = 8065; port <= 8070; port++)
{
cliUris.Add(string.Format("http://localhost:{0}", port));
}
RedirectUris = cliUris;
PostLogoutRedirectUris = cliUris;
}
else if (id == "mobile")
{
RedirectUris = new[] { "bitwarden://sso-callback" };
PostLogoutRedirectUris = new[] { "bitwarden://logged-out" };
}
if (scopes == null)
{
scopes = new string[] { "api" };
}
AllowedScopes = scopes;
}
}

View File

@ -1,36 +1,35 @@
using IdentityModel;
using IdentityServer4.Models;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ApiResources
{
public class ApiResources
public static IEnumerable<ApiResource> GetApiResources()
{
public static IEnumerable<ApiResource> GetApiResources()
return new List<ApiResource>
{
return new List<ApiResource>
{
new ApiResource("api", new string[] {
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified,
"sstamp", // security stamp
"premium",
"device",
"orgowner",
"orgadmin",
"orgmanager",
"orguser",
"orgcustom",
"providerprovideradmin",
"providerserviceuser",
}),
new ApiResource("internal", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.push", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.licensing", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.organization", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.provider", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.installation", new string[] { JwtClaimTypes.Subject }),
};
}
new ApiResource("api", new string[] {
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified,
"sstamp", // security stamp
"premium",
"device",
"orgowner",
"orgadmin",
"orgmanager",
"orguser",
"orgcustom",
"providerprovideradmin",
"providerserviceuser",
}),
new ApiResource("internal", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.push", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.licensing", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.organization", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.provider", new string[] { JwtClaimTypes.Subject }),
new ApiResource("api.installation", new string[] { JwtClaimTypes.Subject }),
};
}
}

View File

@ -1,20 +1,19 @@
using IdentityServer4.Models;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ApiScopes
{
public class ApiScopes
public static IEnumerable<ApiScope> GetApiScopes()
{
public static IEnumerable<ApiScope> GetApiScopes()
return new List<ApiScope>
{
return new List<ApiScope>
{
new ApiScope("api", "API Access"),
new ApiScope("api.push", "API Push Access"),
new ApiScope("api.licensing", "API Licensing Access"),
new ApiScope("api.organization", "API Organization Access"),
new ApiScope("api.installation", "API Installation Access"),
new ApiScope("internal", "Internal Access")
};
}
new ApiScope("api", "API Access"),
new ApiScope("api.push", "API Push Access"),
new ApiScope("api.licensing", "API Licensing Access"),
new ApiScope("api.organization", "API Organization Access"),
new ApiScope("api.installation", "API Installation Access"),
new ApiScope("internal", "Internal Access")
};
}
}

View File

@ -6,39 +6,38 @@ using IdentityServer4.Stores;
using IdentityServer4.Stores.Serialization;
using Microsoft.Extensions.Logging;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
// ref: https://raw.githubusercontent.com/IdentityServer/IdentityServer4/3.1.3/src/IdentityServer4/src/Stores/Default/DefaultAuthorizationCodeStore.cs
public class AuthorizationCodeStore : DefaultGrantStore<AuthorizationCode>, IAuthorizationCodeStore
{
// ref: https://raw.githubusercontent.com/IdentityServer/IdentityServer4/3.1.3/src/IdentityServer4/src/Stores/Default/DefaultAuthorizationCodeStore.cs
public class AuthorizationCodeStore : DefaultGrantStore<AuthorizationCode>, IAuthorizationCodeStore
public AuthorizationCodeStore(
IPersistedGrantStore store,
IPersistentGrantSerializer serializer,
IHandleGenerationService handleGenerationService,
ILogger<DefaultAuthorizationCodeStore> logger)
: base(IdentityServerConstants.PersistedGrantTypes.AuthorizationCode, store, serializer,
handleGenerationService, logger)
{ }
public Task<string> StoreAuthorizationCodeAsync(AuthorizationCode code)
{
public AuthorizationCodeStore(
IPersistedGrantStore store,
IPersistentGrantSerializer serializer,
IHandleGenerationService handleGenerationService,
ILogger<DefaultAuthorizationCodeStore> logger)
: base(IdentityServerConstants.PersistedGrantTypes.AuthorizationCode, store, serializer,
handleGenerationService, logger)
{ }
return CreateItemAsync(code, code.ClientId, code.Subject.GetSubjectId(), code.SessionId,
code.Description, code.CreationTime, code.Lifetime);
}
public Task<string> StoreAuthorizationCodeAsync(AuthorizationCode code)
{
return CreateItemAsync(code, code.ClientId, code.Subject.GetSubjectId(), code.SessionId,
code.Description, code.CreationTime, code.Lifetime);
}
public Task<AuthorizationCode> GetAuthorizationCodeAsync(string code)
{
return GetItemAsync(code);
}
public Task<AuthorizationCode> GetAuthorizationCodeAsync(string code)
{
return GetItemAsync(code);
}
public Task RemoveAuthorizationCodeAsync(string code)
{
// return RemoveItemAsync(code);
public Task RemoveAuthorizationCodeAsync(string code)
{
// return RemoveItemAsync(code);
// We don't want to delete authorization codes during validation.
// We'll rely on the authorization code lifecycle for short term validation and the
// DatabaseExpiredGrantsJob to clean up old authorization codes.
return Task.FromResult(0);
}
// We don't want to delete authorization codes during validation.
// We'll rely on the authorization code lifecycle for short term validation and the
// DatabaseExpiredGrantsJob to clean up old authorization codes.
return Task.FromResult(0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,172 +10,171 @@ using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Stores;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ClientStore : IClientStore
{
public class ClientStore : IClientStore
private readonly IInstallationRepository _installationRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserRepository _userRepository;
private readonly GlobalSettings _globalSettings;
private readonly StaticClientStore _staticClientStore;
private readonly ILicensingService _licensingService;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public ClientStore(
IInstallationRepository installationRepository,
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
GlobalSettings globalSettings,
StaticClientStore staticClientStore,
ILicensingService licensingService,
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IOrganizationApiKeyRepository organizationApiKeyRepository)
{
private readonly IInstallationRepository _installationRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserRepository _userRepository;
private readonly GlobalSettings _globalSettings;
private readonly StaticClientStore _staticClientStore;
private readonly ILicensingService _licensingService;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
_installationRepository = installationRepository;
_organizationRepository = organizationRepository;
_userRepository = userRepository;
_globalSettings = globalSettings;
_staticClientStore = staticClientStore;
_licensingService = licensingService;
_currentContext = currentContext;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public ClientStore(
IInstallationRepository installationRepository,
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
GlobalSettings globalSettings,
StaticClientStore staticClientStore,
ILicensingService licensingService,
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IOrganizationApiKeyRepository organizationApiKeyRepository)
public async Task<Client> FindClientByIdAsync(string clientId)
{
if (!_globalSettings.SelfHosted && clientId.StartsWith("installation."))
{
_installationRepository = installationRepository;
_organizationRepository = organizationRepository;
_userRepository = userRepository;
_globalSettings = globalSettings;
_staticClientStore = staticClientStore;
_licensingService = licensingService;
_currentContext = currentContext;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public async Task<Client> FindClientByIdAsync(string clientId)
{
if (!_globalSettings.SelfHosted && clientId.StartsWith("installation."))
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out Guid id))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out Guid id))
var installation = await _installationRepository.GetByIdAsync(id);
if (installation != null)
{
var installation = await _installationRepository.GetByIdAsync(id);
if (installation != null)
return new Client
{
return new Client
ClientId = $"installation.{installation.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(installation.Key.Sha256()) },
AllowedScopes = new string[] { "api.push", "api.licensing", "api.installation" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = installation.Enabled,
Claims = new List<ClientClaim>
{
ClientId = $"installation.{installation.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(installation.Key.Sha256()) },
AllowedScopes = new string[] { "api.push", "api.licensing", "api.installation" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = installation.Enabled,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, installation.Id.ToString())
}
};
}
}
}
else if (_globalSettings.SelfHosted && clientId.StartsWith("internal.") &&
CoreHelpers.SettingHasValue(_globalSettings.InternalIdentityKey))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1)
{
var id = idParts[1];
if (!string.IsNullOrWhiteSpace(id))
{
return new Client
{
ClientId = $"internal.{id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(_globalSettings.InternalIdentityKey.Sha256()) },
AllowedScopes = new string[] { "internal" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = true,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, id)
}
};
}
}
}
else if (clientId.StartsWith("organization."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var org = await _organizationRepository.GetByIdAsync(id);
if (org != null)
{
var orgApiKey = (await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(org.Id, OrganizationApiKeyType.Default))
.First();
return new Client
{
ClientId = $"organization.{org.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(orgApiKey.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api.organization" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
Enabled = org.Enabled && org.UseApi,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, org.Id.ToString())
}
};
}
}
}
else if (clientId.StartsWith("user."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var user = await _userRepository.GetByIdAsync(id);
if (user != null)
{
var claims = new Collection<ClientClaim>()
{
new ClientClaim(JwtClaimTypes.Subject, user.Id.ToString()),
new ClientClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external")
};
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
claims.Add(isBool ?
new ClientClaim(claim.Key, claim.Value, ClaimValueTypes.Boolean) :
new ClientClaim(claim.Key, claim.Value)
);
new ClientClaim(JwtClaimTypes.Subject, installation.Id.ToString())
}
return new Client
{
ClientId = clientId,
RequireClientSecret = true,
ClientSecrets = { new Secret(user.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
ClientClaimsPrefix = null,
Claims = claims
};
}
};
}
}
return _staticClientStore.ApiClients.ContainsKey(clientId) ?
_staticClientStore.ApiClients[clientId] : null;
}
else if (_globalSettings.SelfHosted && clientId.StartsWith("internal.") &&
CoreHelpers.SettingHasValue(_globalSettings.InternalIdentityKey))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1)
{
var id = idParts[1];
if (!string.IsNullOrWhiteSpace(id))
{
return new Client
{
ClientId = $"internal.{id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(_globalSettings.InternalIdentityKey.Sha256()) },
AllowedScopes = new string[] { "internal" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = true,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, id)
}
};
}
}
}
else if (clientId.StartsWith("organization."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var org = await _organizationRepository.GetByIdAsync(id);
if (org != null)
{
var orgApiKey = (await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(org.Id, OrganizationApiKeyType.Default))
.First();
return new Client
{
ClientId = $"organization.{org.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(orgApiKey.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api.organization" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
Enabled = org.Enabled && org.UseApi,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, org.Id.ToString())
}
};
}
}
}
else if (clientId.StartsWith("user."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var user = await _userRepository.GetByIdAsync(id);
if (user != null)
{
var claims = new Collection<ClientClaim>()
{
new ClientClaim(JwtClaimTypes.Subject, user.Id.ToString()),
new ClientClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external")
};
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
claims.Add(isBool ?
new ClientClaim(claim.Key, claim.Value, ClaimValueTypes.Boolean) :
new ClientClaim(claim.Key, claim.Value)
);
}
return new Client
{
ClientId = clientId,
RequireClientSecret = true,
ClientSecrets = { new Secret(user.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
ClientClaimsPrefix = null,
Claims = claims
};
}
}
}
return _staticClientStore.ApiClients.ContainsKey(clientId) ?
_staticClientStore.ApiClients[clientId] : null;
}
}

View File

@ -5,49 +5,48 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Options;
namespace Bit.Core.IdentityServer
{
public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<CookieAuthenticationOptions>
{
private readonly IdentityServerOptions _idsrv;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly GlobalSettings _globalSettings;
namespace Bit.Core.IdentityServer;
public ConfigureOpenIdConnectDistributedOptions(IHttpContextAccessor httpContextAccessor, GlobalSettings globalSettings,
IdentityServerOptions idsrv)
public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<CookieAuthenticationOptions>
{
private readonly IdentityServerOptions _idsrv;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly GlobalSettings _globalSettings;
public ConfigureOpenIdConnectDistributedOptions(IHttpContextAccessor httpContextAccessor, GlobalSettings globalSettings,
IdentityServerOptions idsrv)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
_globalSettings = globalSettings;
_idsrv = idsrv;
}
public void PostConfigure(string name, CookieAuthenticationOptions options)
{
options.CookieManager = new DistributedCacheCookieManager();
if (name != AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
_globalSettings = globalSettings;
_idsrv = idsrv;
// Ignore
return;
}
public void PostConfigure(string name, CookieAuthenticationOptions options)
options.Cookie.Name = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = _idsrv.Authentication.CookieSameSiteMode;
options.TicketDataFormat = new DistributedCacheTicketDataFormatter(_httpContextAccessor, name);
if (string.IsNullOrWhiteSpace(_globalSettings.IdentityServer?.RedisConnectionString))
{
options.CookieManager = new DistributedCacheCookieManager();
if (name != AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
options.SessionStore = new MemoryCacheTicketStore();
}
else
{
var redisOptions = new RedisCacheOptions
{
// Ignore
return;
}
options.Cookie.Name = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = _idsrv.Authentication.CookieSameSiteMode;
options.TicketDataFormat = new DistributedCacheTicketDataFormatter(_httpContextAccessor, name);
if (string.IsNullOrWhiteSpace(_globalSettings.IdentityServer?.RedisConnectionString))
{
options.SessionStore = new MemoryCacheTicketStore();
}
else
{
var redisOptions = new RedisCacheOptions
{
Configuration = _globalSettings.IdentityServer.RedisConnectionString,
};
options.SessionStore = new RedisCacheTicketStore(redisOptions);
}
Configuration = _globalSettings.IdentityServer.RedisConnectionString,
};
options.SessionStore = new RedisCacheTicketStore(redisOptions);
}
}
}

View File

@ -11,143 +11,142 @@ using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenRequestValidationContext>,
ICustomTokenRequestValidator
{
public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenRequestValidationContext>,
ICustomTokenRequestValidator
private UserManager<User> _userManager;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IOrganizationRepository _organizationRepository;
public CustomTokenRequestValidator(
UserManager<User> userManager,
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService,
IEventService eventService,
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository,
ICaptchaValidationService captchaValidationService)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
userRepository, captchaValidationService)
{
private UserManager<User> _userManager;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IOrganizationRepository _organizationRepository;
_userManager = userManager;
_ssoConfigRepository = ssoConfigRepository;
_organizationRepository = organizationRepository;
}
public CustomTokenRequestValidator(
UserManager<User> userManager,
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService,
IEventService eventService,
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository,
ICaptchaValidationService captchaValidationService)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
userRepository, captchaValidationService)
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
{
string[] allowedGrantTypes = { "authorization_code", "client_credentials" };
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation"))
{
_userManager = userManager;
_ssoConfigRepository = ssoConfigRepository;
_organizationRepository = organizationRepository;
return;
}
await ValidateAsync(context, context.Result.ValidatedRequest,
new CustomValidatorRequestContext { KnownDevice = true });
}
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
protected async override Task<bool> ValidateContextAsync(CustomTokenRequestValidationContext context,
CustomValidatorRequestContext validatorContext)
{
var email = context.Result.ValidatedRequest.Subject?.GetDisplayName()
?? context.Result.ValidatedRequest.ClientClaims?.FirstOrDefault(claim => claim.Type == JwtClaimTypes.Email)?.Value;
if (!string.IsNullOrWhiteSpace(email))
{
string[] allowedGrantTypes = { "authorization_code", "client_credentials" };
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation"))
{
return;
}
await ValidateAsync(context, context.Result.ValidatedRequest,
new CustomValidatorRequestContext { KnownDevice = true });
validatorContext.User = await _userManager.FindByEmailAsync(email);
}
return validatorContext.User != null;
}
protected async override Task<bool> ValidateContextAsync(CustomTokenRequestValidationContext context,
CustomValidatorRequestContext validatorContext)
protected override async Task SetSuccessResult(CustomTokenRequestValidationContext context, User user,
List<Claim> claims, Dictionary<string, object> customResponse)
{
context.Result.CustomResponse = customResponse;
if (claims?.Any() ?? false)
{
var email = context.Result.ValidatedRequest.Subject?.GetDisplayName()
?? context.Result.ValidatedRequest.ClientClaims?.FirstOrDefault(claim => claim.Type == JwtClaimTypes.Email)?.Value;
if (!string.IsNullOrWhiteSpace(email))
context.Result.ValidatedRequest.Client.AlwaysSendClientClaims = true;
context.Result.ValidatedRequest.Client.ClientClaimsPrefix = string.Empty;
foreach (var claim in claims)
{
validatorContext.User = await _userManager.FindByEmailAsync(email);
}
return validatorContext.User != null;
}
protected override async Task SetSuccessResult(CustomTokenRequestValidationContext context, User user,
List<Claim> claims, Dictionary<string, object> customResponse)
{
context.Result.CustomResponse = customResponse;
if (claims?.Any() ?? false)
{
context.Result.ValidatedRequest.Client.AlwaysSendClientClaims = true;
context.Result.ValidatedRequest.Client.ClientClaimsPrefix = string.Empty;
foreach (var claim in claims)
{
context.Result.ValidatedRequest.ClientClaims.Add(claim);
}
}
if (context.Result.CustomResponse == null || user.MasterPassword != null)
{
return;
}
// KeyConnector responses below
// Apikey login
if (context.Result.ValidatedRequest.GrantType == "client_credentials")
{
if (user.UsesKeyConnector)
{
// KeyConnectorUrl is configured in the CLI client, we just need to tell the client to use it
context.Result.CustomResponse["ApiUseKeyConnector"] = true;
context.Result.CustomResponse["ResetMasterPassword"] = false;
}
return;
}
// SSO login
var organizationClaim = context.Result.ValidatedRequest.Subject?.FindFirst(c => c.Type == "organizationId");
if (organizationClaim?.Value != null)
{
var organizationId = new Guid(organizationClaim.Value);
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organizationId);
var ssoConfigData = ssoConfig.GetData();
if (ssoConfigData is { KeyConnectorEnabled: true } && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl))
{
context.Result.CustomResponse["KeyConnectorUrl"] = ssoConfigData.KeyConnectorUrl;
// Prevent clients redirecting to set-password
context.Result.CustomResponse["ResetMasterPassword"] = false;
}
context.Result.ValidatedRequest.ClientClaims.Add(claim);
}
}
protected override void SetTwoFactorResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
if (context.Result.CustomResponse == null || user.MasterPassword != null)
{
context.Result.Error = "invalid_grant";
context.Result.ErrorDescription = "Two factor required.";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
return;
}
protected override void SetSsoResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
// KeyConnector responses below
// Apikey login
if (context.Result.ValidatedRequest.GrantType == "client_credentials")
{
context.Result.Error = "invalid_grant";
context.Result.ErrorDescription = "Single Sign on required.";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
if (user.UsesKeyConnector)
{
// KeyConnectorUrl is configured in the CLI client, we just need to tell the client to use it
context.Result.CustomResponse["ApiUseKeyConnector"] = true;
context.Result.CustomResponse["ResetMasterPassword"] = false;
}
return;
}
protected override void SetErrorResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
// SSO login
var organizationClaim = context.Result.ValidatedRequest.Subject?.FindFirst(c => c.Type == "organizationId");
if (organizationClaim?.Value != null)
{
context.Result.Error = "invalid_grant";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
var organizationId = new Guid(organizationClaim.Value);
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organizationId);
var ssoConfigData = ssoConfig.GetData();
if (ssoConfigData is { KeyConnectorEnabled: true } && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl))
{
context.Result.CustomResponse["KeyConnectorUrl"] = ssoConfigData.KeyConnectorUrl;
// Prevent clients redirecting to set-password
context.Result.CustomResponse["ResetMasterPassword"] = false;
}
}
}
protected override void SetTwoFactorResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result.Error = "invalid_grant";
context.Result.ErrorDescription = "Two factor required.";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
}
protected override void SetSsoResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result.Error = "invalid_grant";
context.Result.ErrorDescription = "Single Sign on required.";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
}
protected override void SetErrorResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result.Error = "invalid_grant";
context.Result.IsError = true;
context.Result.CustomResponse = customResponse;
}
}

View File

@ -1,12 +1,11 @@
using Bit.Core.Entities;
using Bit.Core.Models.Business;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class CustomValidatorRequestContext
{
public class CustomValidatorRequestContext
{
public User User { get; set; }
public bool KnownDevice { get; set; }
public CaptchaResponse CaptchaResponse { get; set; }
}
public User User { get; set; }
public bool KnownDevice { get; set; }
public CaptchaResponse CaptchaResponse { get; set; }
}

View File

@ -4,66 +4,65 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class DistributedCacheCookieManager : ICookieManager
{
public class DistributedCacheCookieManager : ICookieManager
private readonly ChunkingCookieManager _cookieManager;
public DistributedCacheCookieManager()
{
private readonly ChunkingCookieManager _cookieManager;
public DistributedCacheCookieManager()
{
_cookieManager = new ChunkingCookieManager();
}
private string CacheKeyPrefix => "cookie-data";
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
var id = Guid.NewGuid().ToString();
var cacheKey = GetKey(key, id);
var expiresUtc = options.Expires ?? DateTimeOffset.UtcNow.AddMinutes(15);
var cacheOptions = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(expiresUtc);
var data = Encoding.UTF8.GetBytes(value);
var cache = GetCache(context);
cache.Set(cacheKey, data, cacheOptions);
// Write the cookie with the identifier as the body
_cookieManager.AppendResponseCookie(context, key, id, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
_cookieManager.DeleteCookie(context, key, options);
var id = GetId(context, key);
if (!string.IsNullOrWhiteSpace(id))
{
var cacheKey = GetKey(key, id);
GetCache(context).Remove(cacheKey);
}
}
public string GetRequestCookie(HttpContext context, string key)
{
var id = GetId(context, key);
if (string.IsNullOrWhiteSpace(id))
{
return null;
}
var cacheKey = GetKey(key, id);
return GetCache(context).GetString(cacheKey);
}
private IDistributedCache GetCache(HttpContext context) =>
context.RequestServices.GetRequiredService<IDistributedCache>();
private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}";
private string GetId(HttpContext context, string key) =>
context.Request.Cookies.ContainsKey(key) ?
context.Request.Cookies[key] : null;
_cookieManager = new ChunkingCookieManager();
}
private string CacheKeyPrefix => "cookie-data";
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
var id = Guid.NewGuid().ToString();
var cacheKey = GetKey(key, id);
var expiresUtc = options.Expires ?? DateTimeOffset.UtcNow.AddMinutes(15);
var cacheOptions = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(expiresUtc);
var data = Encoding.UTF8.GetBytes(value);
var cache = GetCache(context);
cache.Set(cacheKey, data, cacheOptions);
// Write the cookie with the identifier as the body
_cookieManager.AppendResponseCookie(context, key, id, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
_cookieManager.DeleteCookie(context, key, options);
var id = GetId(context, key);
if (!string.IsNullOrWhiteSpace(id))
{
var cacheKey = GetKey(key, id);
GetCache(context).Remove(cacheKey);
}
}
public string GetRequestCookie(HttpContext context, string key)
{
var id = GetId(context, key);
if (string.IsNullOrWhiteSpace(id))
{
return null;
}
var cacheKey = GetKey(key, id);
return GetCache(context).GetString(cacheKey);
}
private IDistributedCache GetCache(HttpContext context) =>
context.RequestServices.GetRequiredService<IDistributedCache>();
private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}";
private string GetId(HttpContext context, string key) =>
context.Request.Cookies.ContainsKey(key) ?
context.Request.Cookies[key] : null;
}

View File

@ -4,62 +4,61 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket>
{
public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket>
private readonly IHttpContextAccessor _httpContext;
private readonly string _name;
public DistributedCacheTicketDataFormatter(IHttpContextAccessor httpContext, string name)
{
private readonly IHttpContextAccessor _httpContext;
private readonly string _name;
_httpContext = httpContext;
_name = name;
}
public DistributedCacheTicketDataFormatter(IHttpContextAccessor httpContext, string name)
private string CacheKeyPrefix => "ticket-data";
private IDistributedCache Cache => _httpContext.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
private IDataProtector Protector => _httpContext.HttpContext.RequestServices.GetRequiredService<IDataProtectionProvider>()
.CreateProtector(CacheKeyPrefix, _name);
public string Protect(AuthenticationTicket data) => Protect(data, null);
public string Protect(AuthenticationTicket data, string purpose)
{
var key = Guid.NewGuid().ToString();
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}";
var expiresUtc = data.Properties.ExpiresUtc ??
DateTimeOffset.UtcNow.AddMinutes(15);
var options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(expiresUtc);
var ticket = TicketSerializer.Default.Serialize(data);
Cache.Set(cacheKey, ticket, options);
return Protector.Protect(key);
}
public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
if (string.IsNullOrWhiteSpace(protectedText))
{
_httpContext = httpContext;
_name = name;
return null;
}
private string CacheKeyPrefix => "ticket-data";
private IDistributedCache Cache => _httpContext.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
private IDataProtector Protector => _httpContext.HttpContext.RequestServices.GetRequiredService<IDataProtectionProvider>()
.CreateProtector(CacheKeyPrefix, _name);
// Decrypt the key and retrieve the data from the cache.
var key = Protector.Unprotect(protectedText);
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}";
var ticket = Cache.Get(cacheKey);
public string Protect(AuthenticationTicket data) => Protect(data, null);
public string Protect(AuthenticationTicket data, string purpose)
if (ticket == null)
{
var key = Guid.NewGuid().ToString();
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}";
var expiresUtc = data.Properties.ExpiresUtc ??
DateTimeOffset.UtcNow.AddMinutes(15);
var options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(expiresUtc);
var ticket = TicketSerializer.Default.Serialize(data);
Cache.Set(cacheKey, ticket, options);
return Protector.Protect(key);
return null;
}
public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
if (string.IsNullOrWhiteSpace(protectedText))
{
return null;
}
// Decrypt the key and retrieve the data from the cache.
var key = Protector.Unprotect(protectedText);
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}";
var ticket = Cache.Get(cacheKey);
if (ticket == null)
{
return null;
}
var data = TicketSerializer.Default.Deserialize(ticket);
return data;
}
var data = TicketSerializer.Default.Deserialize(ticket);
return data;
}
}

View File

@ -2,53 +2,52 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Memory;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class MemoryCacheTicketStore : ITicketStore
{
public class MemoryCacheTicketStore : ITicketStore
private const string _keyPrefix = "auth-";
private readonly IMemoryCache _cache;
public MemoryCacheTicketStore()
{
private const string _keyPrefix = "auth-";
private readonly IMemoryCache _cache;
_cache = new MemoryCache(new MemoryCacheOptions());
}
public MemoryCacheTicketStore()
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new MemoryCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
_cache = new MemoryCache(new MemoryCacheOptions());
options.SetAbsoluteExpiration(expiresUtc.Value);
}
else
{
options.SetSlidingExpiration(TimeSpan.FromMinutes(15));
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
return key;
}
_cache.Set(key, ticket, options);
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new MemoryCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
else
{
options.SetSlidingExpiration(TimeSpan.FromMinutes(15));
}
return Task.FromResult(0);
}
_cache.Set(key, ticket, options);
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
_cache.TryGetValue(key, out AuthenticationTicket ticket);
return Task.FromResult(ticket);
}
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
_cache.TryGetValue(key, out AuthenticationTicket ticket);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
}

View File

@ -2,25 +2,24 @@
using IdentityServer4;
using IdentityServer4.Models;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class OidcIdentityClient : Client
{
public class OidcIdentityClient : Client
public OidcIdentityClient(GlobalSettings globalSettings)
{
public OidcIdentityClient(GlobalSettings globalSettings)
ClientId = "oidc-identity";
RequireClientSecret = true;
RequirePkce = true;
ClientSecrets = new List<Secret> { new Secret(globalSettings.OidcIdentityClientKey.Sha256()) };
AllowedScopes = new string[]
{
ClientId = "oidc-identity";
RequireClientSecret = true;
RequirePkce = true;
ClientSecrets = new List<Secret> { new Secret(globalSettings.OidcIdentityClientKey.Sha256()) };
AllowedScopes = new string[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
AllowedGrantTypes = GrantTypes.Code;
Enabled = true;
RedirectUris = new List<string> { $"{globalSettings.BaseServiceUri.Identity}/signin-oidc" };
RequireConsent = false;
}
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
AllowedGrantTypes = GrantTypes.Code;
Enabled = true;
RedirectUris = new List<string> { $"{globalSettings.BaseServiceUri.Identity}/signin-oidc" };
RequireConsent = false;
}
}

View File

@ -3,86 +3,85 @@ using IdentityServer4.Models;
using IdentityServer4.Stores;
using Grant = Bit.Core.Entities.Grant;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class PersistedGrantStore : IPersistedGrantStore
{
public class PersistedGrantStore : IPersistedGrantStore
private readonly IGrantRepository _grantRepository;
public PersistedGrantStore(
IGrantRepository grantRepository)
{
private readonly IGrantRepository _grantRepository;
_grantRepository = grantRepository;
}
public PersistedGrantStore(
IGrantRepository grantRepository)
public async Task<PersistedGrant> GetAsync(string key)
{
var grant = await _grantRepository.GetByKeyAsync(key);
if (grant == null)
{
_grantRepository = grantRepository;
return null;
}
public async Task<PersistedGrant> GetAsync(string key)
{
var grant = await _grantRepository.GetByKeyAsync(key);
if (grant == null)
{
return null;
}
var pGrant = ToPersistedGrant(grant);
return pGrant;
}
var pGrant = ToPersistedGrant(grant);
return pGrant;
}
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(PersistedGrantFilter filter)
{
var grants = await _grantRepository.GetManyAsync(filter.SubjectId, filter.SessionId,
filter.ClientId, filter.Type);
var pGrants = grants.Select(g => ToPersistedGrant(g));
return pGrants;
}
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(PersistedGrantFilter filter)
{
var grants = await _grantRepository.GetManyAsync(filter.SubjectId, filter.SessionId,
filter.ClientId, filter.Type);
var pGrants = grants.Select(g => ToPersistedGrant(g));
return pGrants;
}
public async Task RemoveAllAsync(PersistedGrantFilter filter)
{
await _grantRepository.DeleteManyAsync(filter.SubjectId, filter.SessionId, filter.ClientId, filter.Type);
}
public async Task RemoveAllAsync(PersistedGrantFilter filter)
{
await _grantRepository.DeleteManyAsync(filter.SubjectId, filter.SessionId, filter.ClientId, filter.Type);
}
public async Task RemoveAsync(string key)
{
await _grantRepository.DeleteByKeyAsync(key);
}
public async Task RemoveAsync(string key)
{
await _grantRepository.DeleteByKeyAsync(key);
}
public async Task StoreAsync(PersistedGrant pGrant)
{
var grant = ToGrant(pGrant);
await _grantRepository.SaveAsync(grant);
}
public async Task StoreAsync(PersistedGrant pGrant)
private Grant ToGrant(PersistedGrant pGrant)
{
return new Grant
{
var grant = ToGrant(pGrant);
await _grantRepository.SaveAsync(grant);
}
Key = pGrant.Key,
Type = pGrant.Type,
SubjectId = pGrant.SubjectId,
SessionId = pGrant.SessionId,
ClientId = pGrant.ClientId,
Description = pGrant.Description,
CreationDate = pGrant.CreationTime,
ExpirationDate = pGrant.Expiration,
ConsumedDate = pGrant.ConsumedTime,
Data = pGrant.Data
};
}
private Grant ToGrant(PersistedGrant pGrant)
private PersistedGrant ToPersistedGrant(Grant grant)
{
return new PersistedGrant
{
return new Grant
{
Key = pGrant.Key,
Type = pGrant.Type,
SubjectId = pGrant.SubjectId,
SessionId = pGrant.SessionId,
ClientId = pGrant.ClientId,
Description = pGrant.Description,
CreationDate = pGrant.CreationTime,
ExpirationDate = pGrant.Expiration,
ConsumedDate = pGrant.ConsumedTime,
Data = pGrant.Data
};
}
private PersistedGrant ToPersistedGrant(Grant grant)
{
return new PersistedGrant
{
Key = grant.Key,
Type = grant.Type,
SubjectId = grant.SubjectId,
SessionId = grant.SessionId,
ClientId = grant.ClientId,
Description = grant.Description,
CreationTime = grant.CreationDate,
Expiration = grant.ExpirationDate,
ConsumedTime = grant.ConsumedDate,
Data = grant.Data
};
}
Key = grant.Key,
Type = grant.Type,
SubjectId = grant.SubjectId,
SessionId = grant.SessionId,
ClientId = grant.ClientId,
Description = grant.Description,
CreationTime = grant.CreationDate,
Expiration = grant.ExpirationDate,
ConsumedTime = grant.ConsumedDate,
Data = grant.Data
};
}
}

View File

@ -6,83 +6,82 @@ using Bit.Core.Utilities;
using IdentityServer4.Models;
using IdentityServer4.Services;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ProfileService : IProfileService
{
public class ProfileService : IProfileService
private readonly IUserService _userService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly ILicensingService _licensingService;
private readonly ICurrentContext _currentContext;
public ProfileService(
IUserService userService,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
ILicensingService licensingService,
ICurrentContext currentContext)
{
private readonly IUserService _userService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly ILicensingService _licensingService;
private readonly ICurrentContext _currentContext;
_userService = userService;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_licensingService = licensingService;
_currentContext = currentContext;
}
public ProfileService(
IUserService userService,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
ILicensingService licensingService,
ICurrentContext currentContext)
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var existingClaims = context.Subject.Claims;
var newClaims = new List<Claim>();
var user = await _userService.GetUserByPrincipalAsync(context.Subject);
if (user != null)
{
_userService = userService;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_licensingService = licensingService;
_currentContext = currentContext;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var existingClaims = context.Subject.Claims;
var newClaims = new List<Claim>();
var user = await _userService.GetUserByPrincipalAsync(context.Subject);
if (user != null)
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
newClaims.Add(isBool ?
new Claim(claim.Key, claim.Value, ClaimValueTypes.Boolean) :
new Claim(claim.Key, claim.Value)
);
}
}
// filter out any of the new claims
var existingClaimsToKeep = existingClaims
.Where(c => !c.Type.StartsWith("org") &&
(newClaims.Count == 0 || !newClaims.Any(nc => nc.Type == c.Type)))
.ToList();
newClaims.AddRange(existingClaimsToKeep);
if (newClaims.Any())
{
context.IssuedClaims.AddRange(newClaims);
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
newClaims.Add(isBool ?
new Claim(claim.Key, claim.Value, ClaimValueTypes.Boolean) :
new Claim(claim.Key, claim.Value)
);
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
var securityTokenClaim = context.Subject?.Claims.FirstOrDefault(c => c.Type == "sstamp");
var user = await _userService.GetUserByPrincipalAsync(context.Subject);
// filter out any of the new claims
var existingClaimsToKeep = existingClaims
.Where(c => !c.Type.StartsWith("org") &&
(newClaims.Count == 0 || !newClaims.Any(nc => nc.Type == c.Type)))
.ToList();
if (user != null && securityTokenClaim != null)
{
context.IsActive = string.Equals(user.SecurityStamp, securityTokenClaim.Value,
StringComparison.InvariantCultureIgnoreCase);
return;
}
else
{
context.IsActive = true;
}
newClaims.AddRange(existingClaimsToKeep);
if (newClaims.Any())
{
context.IssuedClaims.AddRange(newClaims);
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
var securityTokenClaim = context.Subject?.Claims.FirstOrDefault(c => c.Type == "sstamp");
var user = await _userService.GetUserByPrincipalAsync(context.Subject);
if (user != null && securityTokenClaim != null)
{
context.IsActive = string.Equals(user.SecurityStamp, securityTokenClaim.Value,
StringComparison.InvariantCultureIgnoreCase);
return;
}
else
{
context.IsActive = true;
}
}
}

View File

@ -3,63 +3,62 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.StackExchangeRedis;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class RedisCacheTicketStore : ITicketStore
{
public class RedisCacheTicketStore : ITicketStore
private const string _keyPrefix = "auth-";
private readonly IDistributedCache _cache;
public RedisCacheTicketStore(RedisCacheOptions options)
{
private const string _keyPrefix = "auth-";
private readonly IDistributedCache _cache;
_cache = new RedisCache(options);
}
public RedisCacheTicketStore(RedisCacheOptions options)
{
_cache = new RedisCache(options);
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
return key;
}
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc ??
DateTimeOffset.UtcNow.AddMinutes(15);
options.SetAbsoluteExpiration(expiresUtc);
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc ??
DateTimeOffset.UtcNow.AddMinutes(15);
options.SetAbsoluteExpiration(expiresUtc);
var val = SerializeToBytes(ticket);
_cache.Set(key, val, options);
var val = SerializeToBytes(ticket);
_cache.Set(key, val, options);
return Task.FromResult(0);
}
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
var bytes = _cache.Get(key);
ticket = DeserializeFromBytes(bytes);
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
var bytes = _cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}

View File

@ -11,163 +11,162 @@ using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwnerPasswordValidationContext>,
IResourceOwnerPasswordValidator
{
public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwnerPasswordValidationContext>,
IResourceOwnerPasswordValidator
private UserManager<User> _userManager;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly ICaptchaValidationService _captchaValidationService;
public ResourceOwnerPasswordValidator(
UserManager<User> userManager,
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService,
IEventService eventService,
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
ICaptchaValidationService captchaValidationService,
IUserRepository userRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
userRepository, captchaValidationService)
{
private UserManager<User> _userManager;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly ICaptchaValidationService _captchaValidationService;
public ResourceOwnerPasswordValidator(
UserManager<User> userManager,
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService,
IEventService eventService,
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService,
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
ICaptchaValidationService captchaValidationService,
IUserRepository userRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
userRepository, captchaValidationService)
_userManager = userManager;
_userService = userService;
_currentContext = currentContext;
_captchaValidationService = captchaValidationService;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
if (!AuthEmailHeaderIsValid(context))
{
_userManager = userManager;
_userService = userService;
_currentContext = currentContext;
_captchaValidationService = captchaValidationService;
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
"Auth-Email header invalid.");
return;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
var validatorContext = new CustomValidatorRequestContext
{
if (!AuthEmailHeaderIsValid(context))
User = user,
KnownDevice = await KnownDeviceAsync(user, context.Request)
};
string bypassToken = null;
if (!validatorContext.KnownDevice &&
_captchaValidationService.RequireCaptchaValidation(_currentContext, user))
{
var captchaResponse = context.Request.Raw["captchaResponse"]?.ToString();
if (string.IsNullOrWhiteSpace(captchaResponse))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
"Auth-Email header invalid.");
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Captcha required.",
new Dictionary<string, object>
{
{ _captchaValidationService.SiteKeyResponseKeyName, _captchaValidationService.SiteKey },
});
return;
}
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
var validatorContext = new CustomValidatorRequestContext
validatorContext.CaptchaResponse = await _captchaValidationService.ValidateCaptchaResponseAsync(
captchaResponse, _currentContext.IpAddress, user);
if (!validatorContext.CaptchaResponse.Success)
{
User = user,
KnownDevice = await KnownDeviceAsync(user, context.Request)
};
string bypassToken = null;
if (!validatorContext.KnownDevice &&
_captchaValidationService.RequireCaptchaValidation(_currentContext, user))
{
var captchaResponse = context.Request.Raw["captchaResponse"]?.ToString();
await BuildErrorResultAsync("Captcha is invalid. Please refresh and try again", false, context, null);
return;
}
bypassToken = _captchaValidationService.GenerateCaptchaBypassToken(user);
}
if (string.IsNullOrWhiteSpace(captchaResponse))
await ValidateAsync(context, context.Request, validatorContext);
if (context.Result.CustomResponse != null && bypassToken != null)
{
context.Result.CustomResponse["CaptchaBypassToken"] = bypassToken;
}
}
protected async override Task<bool> ValidateContextAsync(ResourceOwnerPasswordValidationContext context,
CustomValidatorRequestContext validatorContext)
{
if (string.IsNullOrWhiteSpace(context.UserName) || validatorContext.User == null)
{
return false;
}
if (!await _userService.CheckPasswordAsync(validatorContext.User, context.Password))
{
return false;
}
return true;
}
protected override Task SetSuccessResult(ResourceOwnerPasswordValidationContext context, User user,
List<Claim> claims, Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(user.Id.ToString(), "Application",
identityProvider: "bitwarden",
claims: claims.Count > 0 ? claims : null,
customResponse: customResponse);
return Task.CompletedTask;
}
protected override void SetTwoFactorResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor required.",
customResponse);
}
protected override void SetSsoResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Sso authentication required.",
customResponse);
}
protected override void SetErrorResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse: customResponse);
}
private bool AuthEmailHeaderIsValid(ResourceOwnerPasswordValidationContext context)
{
if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Auth-Email"))
{
return false;
}
else
{
try
{
var authEmailHeader = _currentContext.HttpContext.Request.Headers["Auth-Email"];
var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
if (authEmailDecoded != context.UserName)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Captcha required.",
new Dictionary<string, object>
{
{ _captchaValidationService.SiteKeyResponseKeyName, _captchaValidationService.SiteKey },
});
return;
}
validatorContext.CaptchaResponse = await _captchaValidationService.ValidateCaptchaResponseAsync(
captchaResponse, _currentContext.IpAddress, user);
if (!validatorContext.CaptchaResponse.Success)
{
await BuildErrorResultAsync("Captcha is invalid. Please refresh and try again", false, context, null);
return;
}
bypassToken = _captchaValidationService.GenerateCaptchaBypassToken(user);
}
await ValidateAsync(context, context.Request, validatorContext);
if (context.Result.CustomResponse != null && bypassToken != null)
{
context.Result.CustomResponse["CaptchaBypassToken"] = bypassToken;
}
}
protected async override Task<bool> ValidateContextAsync(ResourceOwnerPasswordValidationContext context,
CustomValidatorRequestContext validatorContext)
{
if (string.IsNullOrWhiteSpace(context.UserName) || validatorContext.User == null)
{
return false;
}
if (!await _userService.CheckPasswordAsync(validatorContext.User, context.Password))
{
return false;
}
return true;
}
protected override Task SetSuccessResult(ResourceOwnerPasswordValidationContext context, User user,
List<Claim> claims, Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(user.Id.ToString(), "Application",
identityProvider: "bitwarden",
claims: claims.Count > 0 ? claims : null,
customResponse: customResponse);
return Task.CompletedTask;
}
protected override void SetTwoFactorResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor required.",
customResponse);
}
protected override void SetSsoResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Sso authentication required.",
customResponse);
}
protected override void SetErrorResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse: customResponse);
}
private bool AuthEmailHeaderIsValid(ResourceOwnerPasswordValidationContext context)
{
if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Auth-Email"))
{
return false;
}
else
{
try
{
var authEmailHeader = _currentContext.HttpContext.Request.Headers["Auth-Email"];
var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
if (authEmailDecoded != context.UserName)
{
return false;
}
}
catch (System.Exception e) when (e is System.InvalidOperationException || e is System.FormatException)
{
// Invalid B64 encoding
return false;
}
}
return true;
catch (System.Exception e) when (e is System.InvalidOperationException || e is System.FormatException)
{
// Invalid B64 encoding
return false;
}
}
return true;
}
}

View File

@ -2,23 +2,22 @@
using Bit.Core.Settings;
using IdentityServer4.Models;
namespace Bit.Core.IdentityServer
{
public class StaticClientStore
{
public StaticClientStore(GlobalSettings globalSettings)
{
ApiClients = new List<Client>
{
new ApiClient(globalSettings, BitwardenClient.Mobile, 90, 1),
new ApiClient(globalSettings, BitwardenClient.Web, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Browser, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Desktop, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Cli, 30, 1),
new ApiClient(globalSettings, BitwardenClient.DirectoryConnector, 30, 24)
}.ToDictionary(c => c.ClientId);
}
namespace Bit.Core.IdentityServer;
public IDictionary<string, Client> ApiClients { get; private set; }
public class StaticClientStore
{
public StaticClientStore(GlobalSettings globalSettings)
{
ApiClients = new List<Client>
{
new ApiClient(globalSettings, BitwardenClient.Mobile, 90, 1),
new ApiClient(globalSettings, BitwardenClient.Web, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Browser, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Desktop, 30, 1),
new ApiClient(globalSettings, BitwardenClient.Cli, 30, 1),
new ApiClient(globalSettings, BitwardenClient.DirectoryConnector, 30, 24)
}.ToDictionary(c => c.ClientId);
}
public IDictionary<string, Client> ApiClients { get; private set; }
}

View File

@ -1,30 +1,29 @@
using Microsoft.AspNetCore.Http;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public static class TokenRetrieval
{
public static class TokenRetrieval
private static string _headerScheme = "Bearer ";
private static string _queuryScheme = "access_token";
private static string _authHeader = "Authorization";
public static Func<HttpRequest, string> FromAuthorizationHeaderOrQueryString()
{
private static string _headerScheme = "Bearer ";
private static string _queuryScheme = "access_token";
private static string _authHeader = "Authorization";
public static Func<HttpRequest, string> FromAuthorizationHeaderOrQueryString()
return (request) =>
{
return (request) =>
var authorization = request.Headers[_authHeader].FirstOrDefault();
if (string.IsNullOrWhiteSpace(authorization))
{
var authorization = request.Headers[_authHeader].FirstOrDefault();
if (string.IsNullOrWhiteSpace(authorization))
{
return request.Query[_queuryScheme].FirstOrDefault();
}
return request.Query[_queuryScheme].FirstOrDefault();
}
if (authorization.StartsWith(_headerScheme, StringComparison.OrdinalIgnoreCase))
{
return authorization.Substring(_headerScheme.Length).Trim();
}
if (authorization.StartsWith(_headerScheme, StringComparison.OrdinalIgnoreCase))
{
return authorization.Substring(_headerScheme.Length).Trim();
}
return null;
};
}
return null;
};
}
}

View File

@ -2,20 +2,19 @@
using Bit.Core.Utilities;
using IdentityServer4.Services;
namespace Bit.Core.IdentityServer
namespace Bit.Core.IdentityServer;
public class CustomCorsPolicyService : ICorsPolicyService
{
public class CustomCorsPolicyService : ICorsPolicyService
private readonly GlobalSettings _globalSettings;
public CustomCorsPolicyService(GlobalSettings globalSettings)
{
private readonly GlobalSettings _globalSettings;
_globalSettings = globalSettings;
}
public CustomCorsPolicyService(GlobalSettings globalSettings)
{
_globalSettings = globalSettings;
}
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(CoreHelpers.IsCorsOriginAllowed(origin, _globalSettings));
}
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(CoreHelpers.IsCorsOriginAllowed(origin, _globalSettings));
}
}