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:
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user