mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -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
|
||||
var result = await HttpContext.AuthenticateAsync(
|
||||
IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
if (result?.Succeeded != true)
|
||||
{
|
||||
throw new Exception(_i18nService.T("ExternalAuthenticationError"));
|
||||
@ -249,7 +249,7 @@ namespace Bit.Sso.Controllers
|
||||
}
|
||||
|
||||
// Delete temporary cookie used during external authentication
|
||||
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
await HttpContext.SignOutAsync(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
|
||||
// Retrieve return URL
|
||||
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
||||
|
@ -7,6 +7,7 @@
|
||||
<UserSecretsId>bitwarden-Sso</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso' " />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.7.0" />
|
||||
</ItemGroup>
|
||||
|
@ -58,7 +58,9 @@ namespace Bit.Sso
|
||||
}
|
||||
|
||||
// Authentication
|
||||
services.AddAuthentication();
|
||||
services.AddDistributedIdentityServices(globalSettings);
|
||||
services.AddAuthentication()
|
||||
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
services.AddSsoServices(globalSettings);
|
||||
|
||||
// IdentityServer
|
||||
|
@ -14,8 +14,10 @@ using Bit.Sso.Models;
|
||||
using Bit.Sso.Utilities;
|
||||
using IdentityModel;
|
||||
using IdentityServer4;
|
||||
using IdentityServer4.Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
@ -40,6 +42,7 @@ namespace Bit.Core.Business.Sso
|
||||
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedSchemes;
|
||||
private readonly Dictionary<string, DynamicAuthenticationScheme> _cachedHandlerSchemes;
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
private DateTime? _lastSchemeLoad;
|
||||
private IEnumerable<DynamicAuthenticationScheme> _schemesCopy = Array.Empty<DynamicAuthenticationScheme>();
|
||||
@ -54,7 +57,8 @@ namespace Bit.Core.Business.Sso
|
||||
ISsoConfigRepository ssoConfigRepository,
|
||||
ILogger<DynamicAuthenticationSchemeProvider> logger,
|
||||
GlobalSettings globalSettings,
|
||||
SamlEnvironment samlEnvironment)
|
||||
SamlEnvironment samlEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(options)
|
||||
{
|
||||
_oidcPostConfigureOptions = oidcPostConfigureOptions;
|
||||
@ -81,6 +85,7 @@ namespace Bit.Core.Business.Sso
|
||||
_cachedSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
||||
_cachedHandlerSchemes = new Dictionary<string, DynamicAuthenticationScheme>();
|
||||
_semaphore = new SemaphoreSlim(1);
|
||||
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||
}
|
||||
|
||||
private bool CacheIsValid
|
||||
@ -304,7 +309,7 @@ namespace Bit.Core.Business.Sso
|
||||
ClientSecret = config.ClientSecret,
|
||||
ResponseType = "code",
|
||||
ResponseMode = "form_post",
|
||||
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
|
||||
SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme,
|
||||
SignOutScheme = IdentityServerConstants.SignoutScheme,
|
||||
SaveTokens = false, // reduce overall request size
|
||||
TokenValidationParameters = new TokenValidationParameters
|
||||
@ -332,6 +337,8 @@ namespace Bit.Core.Business.Sso
|
||||
oidcOptions.Scope.Add(OpenIdConnectScopes.Profile);
|
||||
}
|
||||
|
||||
oidcOptions.StateDataFormat = new DistributedCacheStateDataFormatter(_httpContextAccessor, name);
|
||||
|
||||
return new DynamicAuthenticationScheme(name, name, typeof(OpenIdConnectHandler),
|
||||
oidcOptions, SsoType.OpenIdConnect);
|
||||
}
|
||||
@ -407,8 +414,9 @@ namespace Bit.Core.Business.Sso
|
||||
var options = new Saml2Options
|
||||
{
|
||||
SPOptions = spOptions,
|
||||
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
|
||||
SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme,
|
||||
SignOutScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme,
|
||||
CookieManager = new IdentityServer.DistributedCacheCookieManager(),
|
||||
};
|
||||
options.IdentityProviders.Add(idp);
|
||||
|
||||
|
@ -33,8 +33,6 @@ namespace Bit.Api
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// Options
|
||||
services.AddOptions();
|
||||
|
||||
|
@ -9,4 +9,9 @@
|
||||
{
|
||||
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="YubicoDotNetClient" Version="1.2.0" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -141,6 +141,7 @@ namespace Bit.Core
|
||||
{
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string CertificatePassword { get; set; }
|
||||
public string RedisConnectionString { get; set; }
|
||||
}
|
||||
|
||||
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()) };
|
||||
AllowedScopes = new string[]
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile
|
||||
};
|
||||
AllowedGrantTypes = GrantTypes.Code;
|
||||
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.IdentityServer;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Resources;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using IdentityServer4.Configuration;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Localization;
|
||||
using Microsoft.Azure.Storage;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Context;
|
||||
using EntityFrameworkRepos = Bit.Core.Repositories.EntityFramework;
|
||||
using NoopRepos = Bit.Core.Repositories.Noop;
|
||||
using System.Threading.Tasks;
|
||||
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
||||
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
|
||||
{
|
||||
@ -502,5 +505,31 @@ namespace Bit.Core.Utilities
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ namespace Bit.Identity.Controllers
|
||||
{
|
||||
// Read external identity from the temporary cookie
|
||||
var result = await HttpContext.AuthenticateAsync(
|
||||
IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
if (result?.Succeeded != true)
|
||||
{
|
||||
throw new Exception("External authentication error");
|
||||
@ -190,7 +190,7 @@ namespace Bit.Identity.Controllers
|
||||
}, localSignInProps);
|
||||
|
||||
// Delete temporary cookie used during external authentication
|
||||
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
await HttpContext.SignOutAsync(Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
|
||||
// Retrieve return URL
|
||||
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
|
||||
|
@ -81,7 +81,9 @@ namespace Bit.Identity
|
||||
|
||||
// Authentication
|
||||
services
|
||||
.AddDistributedIdentityServices(globalSettings)
|
||||
.AddAuthentication()
|
||||
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
|
||||
.AddOpenIdConnect("sso", "Single Sign On", options =>
|
||||
{
|
||||
options.Authority = globalSettings.BaseServiceUri.InternalSso;
|
||||
@ -91,8 +93,10 @@ namespace Bit.Identity
|
||||
options.ClientSecret = globalSettings.OidcIdentityClientKey;
|
||||
options.ResponseMode = "form_post";
|
||||
|
||||
options.SignInScheme = IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme;
|
||||
options.SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
|
||||
options.ResponseType = "code";
|
||||
options.SaveTokens = false;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
|
||||
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
|
||||
{
|
||||
|
Reference in New Issue
Block a user