mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 23:52:50 -05:00
Fix safari sso header size (#1065)
* Safari SSO header size fix - in progress * Cleanup of memoryCacheTicketStore * Redis cache ticket store + registration * Revert some unecessary changes * temp - distributed cookie: idsrv.external * Ticket data cached storage added * OIDC working w/ substantially reduced cookie size * Added distributed cache cookie manager * Removed hybrid OIDC flow * Enable self-hosted folks to use Redis for SSO * Also allow self-hosted to use Redis cont...
This commit is contained in:
@ -204,7 +204,7 @@ namespace Bit.Sso.Controllers
|
|||||||
{
|
{
|
||||||
// Read external identity from the temporary cookie
|
// Read external identity from the temporary cookie
|
||||||
var result = await HttpContext.AuthenticateAsync(
|
var result = await HttpContext.AuthenticateAsync(
|
||||||
IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||||
if (result?.Succeeded != true)
|
if (result?.Succeeded != true)
|
||||||
{
|
{
|
||||||
throw new Exception(_i18nService.T("ExternalAuthenticationError"));
|
throw new Exception(_i18nService.T("ExternalAuthenticationError"));
|
||||||
@ -249,7 +249,7 @@ namespace Bit.Sso.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete temporary cookie used during external authentication
|
// Delete temporary cookie used during external authentication
|
||||||
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
await HttpContext.SignOutAsync(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||||
|
|
||||||
// Retrieve return URL
|
// Retrieve return URL
|
||||||
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<UserSecretsId>bitwarden-Sso</UserSecretsId>
|
<UserSecretsId>bitwarden-Sso</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso' " />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.7.0" />
|
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -58,7 +58,9 @@ namespace Bit.Sso
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
services.AddAuthentication();
|
services.AddDistributedIdentityServices(globalSettings);
|
||||||
|
services.AddAuthentication()
|
||||||
|
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||||
services.AddSsoServices(globalSettings);
|
services.AddSsoServices(globalSettings);
|
||||||
|
|
||||||
// IdentityServer
|
// IdentityServer
|
||||||
|
@ -14,8 +14,10 @@ using Bit.Sso.Models;
|
|||||||
using Bit.Sso.Utilities;
|
using Bit.Sso.Utilities;
|
||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
using IdentityServer4;
|
using IdentityServer4;
|
||||||
|
using IdentityServer4.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@ -40,6 +42,7 @@ namespace Bit.Core.Business.Sso
|
|||||||
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedSchemes;
|
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedSchemes;
|
||||||
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedHandlerSchemes;
|
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedHandlerSchemes;
|
||||||
private readonly SemaphoreSlim _semaphore;
|
private readonly SemaphoreSlim _semaphore;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
private DateTime? _lastSchemeLoad;
|
private DateTime? _lastSchemeLoad;
|
||||||
private IEnumerable<DynamicAuthenticationScheme> _schemesCopy = Array.Empty<DynamicAuthenticationScheme>();
|
private IEnumerable<DynamicAuthenticationScheme> _schemesCopy = Array.Empty<DynamicAuthenticationScheme>();
|
||||||
@ -54,7 +57,8 @@ namespace Bit.Core.Business.Sso
|
|||||||
ISsoConfigRepository ssoConfigRepository,
|
ISsoConfigRepository ssoConfigRepository,
|
||||||
ILogger<DynamicAuthenticationSchemeProvider> logger,
|
ILogger<DynamicAuthenticationSchemeProvider> logger,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
SamlEnvironment samlEnvironment)
|
SamlEnvironment samlEnvironment,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: base(options)
|
: base(options)
|
||||||
{
|
{
|
||||||
_oidcPostConfigureOptions = oidcPostConfigureOptions;
|
_oidcPostConfigureOptions = oidcPostConfigureOptions;
|
||||||
@ -81,6 +85,7 @@ namespace Bit.Core.Business.Sso
|
|||||||
_cachedSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
_cachedSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
||||||
_cachedHandlerSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
_cachedHandlerSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
||||||
_semaphore = new SemaphoreSlim(1);
|
_semaphore = new SemaphoreSlim(1);
|
||||||
|
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CacheIsValid
|
private bool CacheIsValid
|
||||||
@ -304,7 +309,7 @@ namespace Bit.Core.Business.Sso
|
|||||||
ClientSecret = config.ClientSecret,
|
ClientSecret = config.ClientSecret,
|
||||||
ResponseType = "code",
|
ResponseType = "code",
|
||||||
ResponseMode = "form_post",
|
ResponseMode = "form_post",
|
||||||
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
|
SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme,
|
||||||
SignOutScheme = IdentityServerConstants.SignoutScheme,
|
SignOutScheme = IdentityServerConstants.SignoutScheme,
|
||||||
SaveTokens = false, // reduce overall request size
|
SaveTokens = false, // reduce overall request size
|
||||||
TokenValidationParameters = new TokenValidationParameters
|
TokenValidationParameters = new TokenValidationParameters
|
||||||
@ -332,6 +337,8 @@ namespace Bit.Core.Business.Sso
|
|||||||
oidcOptions.Scope.Add(OpenIdConnectScopes.Profile);
|
oidcOptions.Scope.Add(OpenIdConnectScopes.Profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oidcOptions.StateDataFormat = new DistributedCacheStateDataFormatter(_httpContextAccessor, name);
|
||||||
|
|
||||||
return new DynamicAuthenticationScheme(name, name, typeof(OpenIdConnectHandler),
|
return new DynamicAuthenticationScheme(name, name, typeof(OpenIdConnectHandler),
|
||||||
oidcOptions, SsoType.OpenIdConnect);
|
oidcOptions, SsoType.OpenIdConnect);
|
||||||
}
|
}
|
||||||
@ -407,8 +414,9 @@ namespace Bit.Core.Business.Sso
|
|||||||
var options = new Saml2Options
|
var options = new Saml2Options
|
||||||
{
|
{
|
||||||
SPOptions = spOptions,
|
SPOptions = spOptions,
|
||||||
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
|
SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme,
|
||||||
SignOutScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme,
|
SignOutScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme,
|
||||||
|
CookieManager = new IdentityServer.DistributedCacheCookieManager(),
|
||||||
};
|
};
|
||||||
options.IdentityProviders.Add(idp);
|
options.IdentityProviders.Add(idp);
|
||||||
|
|
||||||
|
@ -33,8 +33,6 @@ namespace Bit.Api
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
var provider = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
|
|
||||||
|
@ -9,4 +9,9 @@
|
|||||||
{
|
{
|
||||||
public const string LinkSso = "LinkSso";
|
public const string LinkSso = "LinkSso";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AuthenticationSchemes
|
||||||
|
{
|
||||||
|
public const string BitwardenExternalCookieAuthenticationScheme = "bw.external";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
<PackageReference Include="Otp.NET" Version="1.2.2" />
|
<PackageReference Include="Otp.NET" Version="1.2.2" />
|
||||||
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
|
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
|
||||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
|
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -141,6 +141,7 @@ namespace Bit.Core
|
|||||||
{
|
{
|
||||||
public string CertificateThumbprint { get; set; }
|
public string CertificateThumbprint { get; set; }
|
||||||
public string CertificatePassword { get; set; }
|
public string CertificatePassword { get; set; }
|
||||||
|
public string RedisConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DataProtectionSettings
|
public class DataProtectionSettings
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using IdentityServer4.Configuration;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Caching.Redis;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
src/Core/IdentityServer/DistributedCacheCookieManager.cs
Normal file
70
src/Core/IdentityServer/DistributedCacheCookieManager.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Bit.Core.IdentityServer
|
||||||
|
{
|
||||||
|
public class DistributedCacheCookieManager : ICookieManager
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Bit.Core.IdentityServer
|
||||||
|
{
|
||||||
|
public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket>
|
||||||
|
{
|
||||||
|
private readonly IHttpContextAccessor _httpContext;
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
public DistributedCacheTicketDataFormatter(IHttpContextAccessor httpContext, string name)
|
||||||
|
{
|
||||||
|
_httpContext = httpContext;
|
||||||
|
_name = 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))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/Core/IdentityServer/MemoryCacheTicketStore.cs
Normal file
56
src/Core/IdentityServer/MemoryCacheTicketStore.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace Bit.Core.IdentityServer
|
||||||
|
{
|
||||||
|
public class MemoryCacheTicketStore : ITicketStore
|
||||||
|
{
|
||||||
|
private const string _keyPrefix = "auth-";
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
|
||||||
|
public MemoryCacheTicketStore()
|
||||||
|
{
|
||||||
|
_cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
options.SetAbsoluteExpiration(expiresUtc.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options.SetSlidingExpiration(TimeSpan.FromMinutes(15));
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache.Set(key, ticket, options);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,8 @@ namespace Bit.Core.IdentityServer
|
|||||||
ClientSecrets = new List<Secret> { new Secret(globalSettings.OidcIdentityClientKey.Sha256()) };
|
ClientSecrets = new List<Secret> { new Secret(globalSettings.OidcIdentityClientKey.Sha256()) };
|
||||||
AllowedScopes = new string[]
|
AllowedScopes = new string[]
|
||||||
{
|
{
|
||||||
IdentityServerConstants.StandardScopes.OpenId,
|
IdentityServerConstants.StandardScopes.OpenId,
|
||||||
IdentityServerConstants.StandardScopes.Profile
|
IdentityServerConstants.StandardScopes.Profile
|
||||||
};
|
};
|
||||||
AllowedGrantTypes = GrantTypes.Code;
|
AllowedGrantTypes = GrantTypes.Code;
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
|
68
src/Core/IdentityServer/RedisCacheTicketStore.cs
Normal file
68
src/Core/IdentityServer/RedisCacheTicketStore.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Caching.Redis;
|
||||||
|
|
||||||
|
namespace Bit.Core.IdentityServer
|
||||||
|
{
|
||||||
|
public class RedisCacheTicketStore : ITicketStore
|
||||||
|
{
|
||||||
|
private const string _keyPrefix = "auth-";
|
||||||
|
private readonly IDistributedCache _cache;
|
||||||
|
|
||||||
|
public RedisCacheTicketStore(RedisCacheOptions options)
|
||||||
|
{
|
||||||
|
_cache = new RedisCache(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 DistributedCacheEntryOptions();
|
||||||
|
var expiresUtc = ticket.Properties.ExpiresUtc ??
|
||||||
|
DateTimeOffset.UtcNow.AddMinutes(15);
|
||||||
|
options.SetAbsoluteExpiration(expiresUtc);
|
||||||
|
|
||||||
|
var val = SerializeToBytes(ticket);
|
||||||
|
_cache.Set(key, val, options);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AuthenticationTicket> RetrieveAsync(string key)
|
||||||
|
{
|
||||||
|
AuthenticationTicket ticket;
|
||||||
|
var bytes = _cache.Get(key);
|
||||||
|
ticket = DeserializeFromBytes(bytes);
|
||||||
|
|
||||||
|
return Task.FromResult(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveAsync(string key)
|
||||||
|
{
|
||||||
|
_cache.Remove(key);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +1,43 @@
|
|||||||
using Bit.Core.Enums;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Identity;
|
using Bit.Core.Identity;
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Resources;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using IdentityServer4.Configuration;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Localization;
|
||||||
|
using Microsoft.Azure.Storage;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System;
|
using Microsoft.Extensions.Hosting;
|
||||||
using System.IO;
|
using Microsoft.Extensions.Options;
|
||||||
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
using Serilog.Context;
|
||||||
using EntityFrameworkRepos = Bit.Core.Repositories.EntityFramework;
|
using EntityFrameworkRepos = Bit.Core.Repositories.EntityFramework;
|
||||||
using NoopRepos = Bit.Core.Repositories.Noop;
|
using NoopRepos = Bit.Core.Repositories.Noop;
|
||||||
using System.Threading.Tasks;
|
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
||||||
using TableStorageRepos = Bit.Core.Repositories.TableStorage;
|
using TableStorageRepos = Bit.Core.Repositories.TableStorage;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using IdentityServer4.AccessTokenValidation;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Serilog.Context;
|
|
||||||
using AutoMapper;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Azure.Storage;
|
|
||||||
using System.Reflection;
|
|
||||||
using Bit.Core.Resources;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Localization;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
namespace Bit.Core.Utilities
|
||||||
{
|
{
|
||||||
@ -502,5 +505,31 @@ namespace Bit.Core.Utilities
|
|||||||
return factory.Create("SharedResources", assemblyName.Name);
|
return factory.Create("SharedResources", assemblyName.Name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddDistributedIdentityServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(globalSettings.IdentityServer?.RedisConnectionString))
|
||||||
|
{
|
||||||
|
services.AddDistributedMemoryCache();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddDistributedRedisCache(options =>
|
||||||
|
options.Configuration = globalSettings.IdentityServer.RedisConnectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddOidcStateDataFormatterCache();
|
||||||
|
services.AddSession();
|
||||||
|
services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
|
||||||
|
services.ConfigureExternalCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
|
||||||
|
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>>(
|
||||||
|
svcs => new ConfigureOpenIdConnectDistributedOptions(
|
||||||
|
svcs.GetRequiredService<IHttpContextAccessor>(),
|
||||||
|
globalSettings,
|
||||||
|
svcs.GetRequiredService<IdentityServerOptions>())
|
||||||
|
);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -153,7 +153,7 @@ namespace Bit.Identity.Controllers
|
|||||||
{
|
{
|
||||||
// Read external identity from the temporary cookie
|
// Read external identity from the temporary cookie
|
||||||
var result = await HttpContext.AuthenticateAsync(
|
var result = await HttpContext.AuthenticateAsync(
|
||||||
IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||||
if (result?.Succeeded != true)
|
if (result?.Succeeded != true)
|
||||||
{
|
{
|
||||||
throw new Exception("External authentication error");
|
throw new Exception("External authentication error");
|
||||||
@ -190,7 +190,7 @@ namespace Bit.Identity.Controllers
|
|||||||
}, localSignInProps);
|
}, localSignInProps);
|
||||||
|
|
||||||
// Delete temporary cookie used during external authentication
|
// Delete temporary cookie used during external authentication
|
||||||
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
await HttpContext.SignOutAsync(Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||||
|
|
||||||
// Retrieve return URL
|
// Retrieve return URL
|
||||||
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
||||||
|
@ -81,7 +81,9 @@ namespace Bit.Identity
|
|||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
services
|
services
|
||||||
|
.AddDistributedIdentityServices(globalSettings)
|
||||||
.AddAuthentication()
|
.AddAuthentication()
|
||||||
|
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
|
||||||
.AddOpenIdConnect("sso", "Single Sign On", options =>
|
.AddOpenIdConnect("sso", "Single Sign On", options =>
|
||||||
{
|
{
|
||||||
options.Authority = globalSettings.BaseServiceUri.InternalSso;
|
options.Authority = globalSettings.BaseServiceUri.InternalSso;
|
||||||
@ -91,8 +93,10 @@ namespace Bit.Identity
|
|||||||
options.ClientSecret = globalSettings.OidcIdentityClientKey;
|
options.ClientSecret = globalSettings.OidcIdentityClientKey;
|
||||||
options.ResponseMode = "form_post";
|
options.ResponseMode = "form_post";
|
||||||
|
|
||||||
options.SignInScheme = IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme;
|
options.SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
|
||||||
options.ResponseType = "code";
|
options.ResponseType = "code";
|
||||||
|
options.SaveTokens = false;
|
||||||
|
options.GetClaimsFromUserInfoEndpoint = true;
|
||||||
|
|
||||||
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
|
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user