1
0
mirror of https://github.com/bitwarden/server synced 2026-01-04 17:43:53 +00:00

Merge branch 'main' into auth/pm-22975/client-version-validator

This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-11-21 15:08:45 -05:00
committed by GitHub
62 changed files with 484 additions and 742 deletions

View File

@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Models.Api.Request.Accounts;
namespace Bit.Core.Auth.Attributes;
public class MarketingInitiativeValidationAttribute : ValidationAttribute
{
private static readonly string[] _acceptedValues = [MarketingInitiativeConstants.Premium];
public MarketingInitiativeValidationAttribute()
{
ErrorMessage = $"Marketing initiative type must be one of: {string.Join(", ", _acceptedValues)}";
}
public override bool IsValid(object? value)
{
if (value == null)
{
return true;
}
if (value is not string str)
{
return false;
}
return _acceptedValues.Contains(str);
}
}

View File

@@ -0,0 +1,10 @@
namespace Bit.Core.Auth.Models.Api.Request.Accounts;
public static class MarketingInitiativeConstants
{
/// <summary>
/// Indicates that the user began the registration process on a marketing page designed
/// to streamline users who intend to setup a premium subscription after registration.
/// </summary>
public const string Premium = "premium";
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Attributes;
using Bit.Core.Utilities;
namespace Bit.Core.Auth.Models.Api.Request.Accounts;
@@ -11,4 +12,6 @@ public class RegisterSendVerificationEmailRequestModel
[StringLength(256)]
public required string Email { get; set; }
public bool ReceiveMarketingEmails { get; set; }
[MarketingInitiativeValidation]
public string? FromMarketing { get; set; }
}

View File

@@ -26,7 +26,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
new(nameof(OrganizationLicenseConstants.LicenseType), LicenseType.Organization.ToString()),
new(nameof(OrganizationLicenseConstants.Id), entity.Id.ToString()),
new(nameof(OrganizationLicenseConstants.Enabled), entity.Enabled.ToString()),
new(nameof(OrganizationLicenseConstants.PlanType), entity.PlanType.ToString()),
new(nameof(OrganizationLicenseConstants.PlanType), ((int)entity.PlanType).ToString()),
new(nameof(OrganizationLicenseConstants.UsePolicies), entity.UsePolicies.ToString()),
new(nameof(OrganizationLicenseConstants.UseSso), entity.UseSso.ToString()),
new(nameof(OrganizationLicenseConstants.UseKeyConnector), entity.UseKeyConnector.ToString()),

View File

@@ -399,7 +399,6 @@ public class OrganizationLicense : ILicense
var installationId = claimsPrincipal.GetValue<Guid>(nameof(InstallationId));
var licenseKey = claimsPrincipal.GetValue<string>(nameof(LicenseKey));
var enabled = claimsPrincipal.GetValue<bool>(nameof(Enabled));
var planType = claimsPrincipal.GetValue<PlanType>(nameof(PlanType));
var seats = claimsPrincipal.GetValue<int?>(nameof(Seats));
var maxCollections = claimsPrincipal.GetValue<short?>(nameof(MaxCollections));
var useGroups = claimsPrincipal.GetValue<bool>(nameof(UseGroups));
@@ -425,12 +424,18 @@ public class OrganizationLicense : ILicense
var useOrganizationDomains = claimsPrincipal.GetValue<bool>(nameof(UseOrganizationDomains));
var useAutomaticUserConfirmation = claimsPrincipal.GetValue<bool>(nameof(UseAutomaticUserConfirmation));
var claimedPlanType = claimsPrincipal.GetValue<PlanType>(nameof(PlanType));
var planTypesMatch = claimedPlanType == PlanType.FamiliesAnnually
? organization.PlanType is PlanType.FamiliesAnnually or PlanType.FamiliesAnnually2025
: organization.PlanType == claimedPlanType;
return issued <= DateTime.UtcNow &&
expires >= DateTime.UtcNow &&
installationId == globalSettings.Installation.Id &&
licenseKey == organization.LicenseKey &&
enabled == organization.Enabled &&
planType == organization.PlanType &&
planTypesMatch &&
seats == organization.Seats &&
maxCollections == organization.MaxCollections &&
useGroups == organization.UseGroups &&

View File

@@ -163,7 +163,7 @@ public static class FeatureFlagKeys
"pm-23174-manage-account-recovery-permission-drives-the-need-to-set-master-password";
public const string RecoveryCodeSupportForSsoRequiredUsers = "pm-21153-recovery-code-support-for-sso-required";
public const string MJMLBasedEmailTemplates = "mjml-based-email-templates";
public const string MjmlWelcomeEmailTemplates = "mjml-welcome-email-templates";
public const string MjmlWelcomeEmailTemplates = "pm-21741-mjml-welcome-email";
public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow";
/* Autofill Team */
@@ -186,7 +186,6 @@ public static class FeatureFlagKeys
/* Billing Team */
public const string TrialPayment = "PM-8163-trial-payment";
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover";
public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings";
public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure";

View File

@@ -50,20 +50,16 @@
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" />
<PackageReference Include="OneOf" Version="3.0.271" />
<PackageReference Include="SendGrid" Version="9.29.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
<PackageReference Include="Sentry.Serilog" Version="5.0.0" />
<PackageReference Include="Duende.IdentityServer" Version="7.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog.Sinks.SyslogMessages" Version="4.0.0" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Braintree" Version="5.28.0" />
<PackageReference Include="Stripe.net" Version="48.5.0" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.10.1" />
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.10.4" />
<PackageReference Include="Quartz" Version="3.14.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.14.0" />

View File

@@ -2,6 +2,7 @@
"packages": [
"components/mj-bw-hero",
"components/mj-bw-icon-row",
"components/mj-bw-learn-more-footer"
"components/mj-bw-learn-more-footer",
"emails/AdminConsole/components/mj-bw-inviter-info"
]
}

View File

@@ -0,0 +1,35 @@
const { BodyComponent } = require("mjml-core");
class MjBwInviterInfo extends BodyComponent {
static dependencies = {
"mj-column": ["mj-bw-inviter-info"],
"mj-wrapper": ["mj-bw-inviter-info"],
"mj-bw-inviter-info": [],
};
static allowedAttributes = {
"expiration-date": "string", // REQUIRED: Date to display
"email-address": "string", // Optional: Email address to display
};
render() {
const emailAddressText = this.getAttribute("email-address")
? `This invitation was sent by <a href="mailto:${this.getAttribute("email-address")}" class="link">${this.getAttribute("email-address")}</a> and expires `
: "This invitation expires ";
return this.renderMJML(
`
<mj-section background-color="#fff" padding="15px 10px 10px 10px">
<mj-column>
<mj-text font-size="12px" line-height="24px" padding="10px 15px">
${emailAddressText + this.getAttribute("expiration-date")}
</mj-text>
</mj-column>
</mj-section>
`
);
}
}
module.exports = MjBwInviterInfo;

View File

@@ -0,0 +1,38 @@
<mj-section background-color="#fff" padding="30px 30px 10px 30px">
<mj-column>
<mj-text font-size="18px" font-weight="700" line-height="32px" padding="0 0 15px 0">
Download Bitwarden on all devices
</mj-text>
<mj-text font-size="15px" line-height="16px" padding="0 0 20px 0">
Already using the <a href="https://bitwarden.com/download/" class="link">browser extension</a>?
Download the Bitwarden mobile app from the
<a href="https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744" class="link">App Store</a>
or <a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" class="link">Google Play</a>
to quickly save logins and autofill forms on the go.
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#fff" padding="0 30px 20px 30px">
<mj-group>
<mj-column width="120px" vertical-align="middle">
<mj-image
href="https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744"
src="https://assets.bitwarden.com/email/v1/App-store.png"
alt="Download on the App Store"
width="120px"
padding="0"
/>
</mj-column>
<mj-column width="150px" vertical-align="middle">
<mj-image
href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden"
src="https://assets.bitwarden.com/email/v1/google-play-badge.png"
alt="Get it on Google Play"
width="150px"
padding="0 0 0 10px"
/>
</mj-column>
</mj-group>
</mj-section>

View File

@@ -4,8 +4,8 @@ namespace Bit.Core.Models.Data;
public class UserKdfInformation
{
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
public required KdfType Kdf { get; set; }
public required int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
}

View File

@@ -2,14 +2,12 @@
#nullable disable
using Bit.Core.Auth.Settings;
using Bit.Core.Settings.LoggingSettings;
namespace Bit.Core.Settings;
public class GlobalSettings : IGlobalSettings
{
private string _mailTemplateDirectory;
private string _logDirectory;
private string _licenseDirectory;
public GlobalSettings()
@@ -25,14 +23,6 @@ public class GlobalSettings : IGlobalSettings
public virtual string KnownProxies { get; set; }
public virtual string SiteName { get; set; }
public virtual string ProjectName { get; set; }
public virtual string LogDirectory
{
get => BuildDirectory(_logDirectory, "/logs");
set => _logDirectory = value;
}
public virtual bool LogDirectoryByProject { get; set; } = true;
public virtual long? LogRollBySizeLimit { get; set; }
public virtual bool EnableDevLogging { get; set; } = false;
public virtual string LicenseDirectory
{
get => BuildDirectory(_licenseDirectory, "/core/licenses");
@@ -73,9 +63,6 @@ public class GlobalSettings : IGlobalSettings
public virtual FileStorageSettings Send { get; set; }
public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings();
public virtual DataProtectionSettings DataProtection { get; set; }
public virtual SentrySettings Sentry { get; set; } = new SentrySettings();
public virtual SyslogSettings Syslog { get; set; } = new SyslogSettings();
public virtual ILogLevelSettings MinLogLevel { get; set; } = new LogLevelSettings();
public virtual NotificationHubPoolSettings NotificationHubPool { get; set; } = new();
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
@@ -548,59 +535,11 @@ public class GlobalSettings : IGlobalSettings
}
}
public class SentrySettings
{
public string Dsn { get; set; }
}
public class NotificationsSettings : ConnectionStringSettings
{
public string RedisConnectionString { get; set; }
}
public class SyslogSettings
{
/// <summary>
/// The connection string used to connect to a remote syslog server over TCP or UDP, or to connect locally.
/// </summary>
/// <remarks>
/// <para>The connection string will be parsed using <see cref="System.Uri" /> to extract the protocol, host name and port number.
/// </para>
/// <para>
/// Supported protocols are:
/// <list type="bullet">
/// <item>UDP (use <code>udp://</code>)</item>
/// <item>TCP (use <code>tcp://</code>)</item>
/// <item>TLS over TCP (use <code>tls://</code>)</item>
/// </list>
/// </para>
/// </remarks>
/// <example>
/// A remote server (logging.dev.example.com) is listening on UDP (port 514):
/// <code>
/// udp://logging.dev.example.com:514</code>.
/// </example>
public string Destination { get; set; }
/// <summary>
/// The absolute path to a Certificate (DER or Base64 encoded with private key).
/// </summary>
/// <remarks>
/// The certificate path and <see cref="CertificatePassword"/> are passed into the <see cref="System.Security.Cryptography.X509Certificates.X509Certificate2.X509Certificate2(string, string)" />.
/// The file format of the certificate may be binary encoded (DER) or base64. If the private key is encrypted, provide the password in <see cref="CertificatePassword"/>,
/// </remarks>
public string CertificatePath { get; set; }
/// <summary>
/// The password for the encrypted private key in the certificate supplied in <see cref="CertificatePath" />.
/// </summary>
/// <value></value>
public string CertificatePassword { get; set; }
/// <summary>
/// The thumbprint of the certificate in the X.509 certificate store for personal certificates for the user account running Bitwarden.
/// </summary>
/// <value></value>
public string CertificateThumbprint { get; set; }
}
public class NotificationHubSettings
{
private string _connectionString;

View File

@@ -20,7 +20,6 @@ public interface IGlobalSettings
IConnectionStringSettings Storage { get; set; }
IBaseServiceUriSettings BaseServiceUri { get; set; }
ISsoSettings Sso { get; set; }
ILogLevelSettings MinLogLevel { get; set; }
IPasswordlessAuthSettings PasswordlessAuth { get; set; }
IDomainVerificationSettings DomainVerification { get; set; }
ILaunchDarklySettings LaunchDarkly { get; set; }

View File

@@ -1,74 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings;
public interface ILogLevelSettings
{
IBillingLogLevelSettings BillingSettings { get; set; }
IApiLogLevelSettings ApiSettings { get; set; }
IIdentityLogLevelSettings IdentitySettings { get; set; }
IScimLogLevelSettings ScimSettings { get; set; }
ISsoLogLevelSettings SsoSettings { get; set; }
IAdminLogLevelSettings AdminSettings { get; set; }
IEventsLogLevelSettings EventsSettings { get; set; }
IEventsProcessorLogLevelSettings EventsProcessorSettings { get; set; }
IIconsLogLevelSettings IconsSettings { get; set; }
INotificationsLogLevelSettings NotificationsSettings { get; set; }
}
public interface IBillingLogLevelSettings
{
LogEventLevel Default { get; set; }
LogEventLevel Jobs { get; set; }
}
public interface IApiLogLevelSettings
{
LogEventLevel Default { get; set; }
LogEventLevel IdentityToken { get; set; }
LogEventLevel IpRateLimit { get; set; }
}
public interface IIdentityLogLevelSettings
{
LogEventLevel Default { get; set; }
LogEventLevel IdentityToken { get; set; }
LogEventLevel IpRateLimit { get; set; }
}
public interface IScimLogLevelSettings
{
LogEventLevel Default { get; set; }
}
public interface ISsoLogLevelSettings
{
LogEventLevel Default { get; set; }
}
public interface IAdminLogLevelSettings
{
LogEventLevel Default { get; set; }
}
public interface IEventsLogLevelSettings
{
LogEventLevel Default { get; set; }
LogEventLevel IdentityToken { get; set; }
}
public interface IEventsProcessorLogLevelSettings
{
LogEventLevel Default { get; set; }
}
public interface IIconsLogLevelSettings
{
LogEventLevel Default { get; set; }
}
public interface INotificationsLogLevelSettings
{
LogEventLevel Default { get; set; }
LogEventLevel IdentityToken { get; set; }
}

View File

@@ -1,8 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class AdminLogLevelSettings : IAdminLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
}

View File

@@ -1,10 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class ApiLogLevelSettings : IApiLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
public LogEventLevel IdentityToken { get; set; } = LogEventLevel.Fatal;
public LogEventLevel IpRateLimit { get; set; } = LogEventLevel.Information;
}

View File

@@ -1,9 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class BillingLogLevelSettings : IBillingLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Warning;
public LogEventLevel Jobs { get; set; } = LogEventLevel.Information;
}

View File

@@ -1,9 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class EventsLogLevelSettings : IEventsLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
public LogEventLevel IdentityToken { get; set; } = LogEventLevel.Fatal;
}

View File

@@ -1,8 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class EventsProcessorLogLevelSettings : IEventsProcessorLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Warning;
}

View File

@@ -1,8 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class IconsLogLevelSettings : IIconsLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
}

View File

@@ -1,10 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class IdentityLogLevelSettings : IIdentityLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
public LogEventLevel IdentityToken { get; set; } = LogEventLevel.Fatal;
public LogEventLevel IpRateLimit { get; set; } = LogEventLevel.Information;
}

View File

@@ -1,16 +0,0 @@

namespace Bit.Core.Settings.LoggingSettings;
public class LogLevelSettings : ILogLevelSettings
{
public IBillingLogLevelSettings BillingSettings { get; set; } = new BillingLogLevelSettings();
public IApiLogLevelSettings ApiSettings { get; set; } = new ApiLogLevelSettings();
public IIdentityLogLevelSettings IdentitySettings { get; set; } = new IdentityLogLevelSettings();
public IScimLogLevelSettings ScimSettings { get; set; } = new ScimLogLevelSettings();
public ISsoLogLevelSettings SsoSettings { get; set; } = new SsoLogLevelSettings();
public IAdminLogLevelSettings AdminSettings { get; set; } = new AdminLogLevelSettings();
public IEventsLogLevelSettings EventsSettings { get; set; } = new EventsLogLevelSettings();
public IEventsProcessorLogLevelSettings EventsProcessorSettings { get; set; } = new EventsProcessorLogLevelSettings();
public IIconsLogLevelSettings IconsSettings { get; set; } = new IconsLogLevelSettings();
public INotificationsLogLevelSettings NotificationsSettings { get; set; } = new NotificationsLogLevelSettings();
}

View File

@@ -1,9 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class NotificationsLogLevelSettings : INotificationsLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Warning;
public LogEventLevel IdentityToken { get; set; } = LogEventLevel.Fatal;
}

View File

@@ -1,8 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class ScimLogLevelSettings : IScimLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Warning;
}

View File

@@ -1,8 +0,0 @@
using Serilog.Events;
namespace Bit.Core.Settings.LoggingSettings;
public class SsoLogLevelSettings : ISsoLogLevelSettings
{
public LogEventLevel Default { get; set; } = LogEventLevel.Error;
}

View File

@@ -1,165 +1,78 @@
using System.Security.Cryptography.X509Certificates;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Syslog;
namespace Bit.Core.Utilities;
public static class LoggerFactoryExtensions
{
public static void UseSerilog(
this IApplicationBuilder appBuilder,
IWebHostEnvironment env,
IHostApplicationLifetime applicationLifetime,
GlobalSettings globalSettings)
/// <summary>
///
/// </summary>
/// <param name="hostBuilder"></param>
/// <returns></returns>
public static IHostBuilder AddSerilogFileLogging(this IHostBuilder hostBuilder)
{
if (env.IsDevelopment() && !globalSettings.EnableDevLogging)
return hostBuilder.ConfigureLogging((context, logging) =>
{
return;
}
applicationLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
}
public static ILoggingBuilder AddSerilog(
this ILoggingBuilder builder,
WebHostBuilderContext context,
Func<LogEvent, IGlobalSettings, bool>? filter = null)
{
var globalSettings = new GlobalSettings();
ConfigurationBinder.Bind(context.Configuration.GetSection("GlobalSettings"), globalSettings);
if (context.HostingEnvironment.IsDevelopment() && !globalSettings.EnableDevLogging)
{
return builder;
}
bool inclusionPredicate(LogEvent e)
{
if (filter == null)
if (context.HostingEnvironment.IsDevelopment())
{
return true;
return;
}
var eventId = e.Properties.TryGetValue("EventId", out var eventIdValue) ? eventIdValue.ToString() : null;
if (eventId?.Contains(Constants.BypassFiltersEventId.ToString()) ?? false)
// If they have begun using the new settings location, use that
if (!string.IsNullOrEmpty(context.Configuration["Logging:PathFormat"]))
{
return true;
}
return filter(e, globalSettings);
}
var logSentryWarning = false;
var logSyslogWarning = false;
// Path format is the only required option for file logging, we will use that as
// the keystone for if they have configured the new location.
var newPathFormat = context.Configuration["Logging:PathFormat"];
var config = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.Filter.ByIncludingOnly(inclusionPredicate);
if (CoreHelpers.SettingHasValue(globalSettings.Sentry.Dsn))
{
config.WriteTo.Sentry(globalSettings.Sentry.Dsn)
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
else if (CoreHelpers.SettingHasValue(globalSettings.Syslog.Destination))
{
logSyslogWarning = true;
// appending sitename to project name to allow easier identification in syslog.
var appName = $"{globalSettings.SiteName}-{globalSettings.ProjectName}";
if (globalSettings.Syslog.Destination.Equals("local", StringComparison.OrdinalIgnoreCase))
{
config.WriteTo.LocalSyslog(appName);
}
else if (Uri.TryCreate(globalSettings.Syslog.Destination, UriKind.Absolute, out var syslogAddress))
{
// Syslog's standard port is 514 (both UDP and TCP). TLS does not have a standard port, so assume 514.
int port = syslogAddress.Port >= 0
? syslogAddress.Port
: 514;
if (syslogAddress.Scheme.Equals("udp"))
{
config.WriteTo.UdpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tcp"))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tls"))
{
if (CoreHelpers.SettingHasValue(globalSettings.Syslog.CertificateThumbprint))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
useTls: true,
certProvider: new CertificateStoreProvider(StoreName.My, StoreLocation.CurrentUser,
globalSettings.Syslog.CertificateThumbprint));
}
else
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
useTls: true,
certProvider: new CertificateFileProvider(globalSettings.Syslog.CertificatePath,
globalSettings.Syslog?.CertificatePassword ?? string.Empty));
}
}
}
}
else if (!string.IsNullOrEmpty(newPathFormat))
{
// Use new location
builder.AddFile(context.Configuration.GetSection("Logging"));
}
else if (CoreHelpers.SettingHasValue(globalSettings.LogDirectory))
{
if (globalSettings.LogRollBySizeLimit.HasValue)
{
var pathFormat = Path.Combine(globalSettings.LogDirectory, $"{globalSettings.ProjectName.ToLowerInvariant()}.log");
if (globalSettings.LogDirectoryByProject)
{
pathFormat = Path.Combine(globalSettings.LogDirectory, globalSettings.ProjectName, "log.txt");
}
config.WriteTo.File(pathFormat, rollOnFileSizeLimit: true,
fileSizeLimitBytes: globalSettings.LogRollBySizeLimit);
logging.AddFile(context.Configuration.GetSection("Logging"));
}
else
{
var pathFormat = Path.Combine(globalSettings.LogDirectory, $"{globalSettings.ProjectName.ToLowerInvariant()}_{{Date}}.log");
if (globalSettings.LogDirectoryByProject)
var globalSettingsSection = context.Configuration.GetSection("GlobalSettings");
var loggingOptions = new LegacyFileLoggingOptions();
globalSettingsSection.Bind(loggingOptions);
if (string.IsNullOrWhiteSpace(loggingOptions.LogDirectory))
{
pathFormat = Path.Combine(globalSettings.LogDirectory, globalSettings.ProjectName, "{Date}.txt");
return;
}
var projectName = loggingOptions.ProjectName
?? context.HostingEnvironment.ApplicationName;
if (loggingOptions.LogRollBySizeLimit.HasValue)
{
var pathFormat = loggingOptions.LogDirectoryByProject
? Path.Combine(loggingOptions.LogDirectory, projectName, "log.txt")
: Path.Combine(loggingOptions.LogDirectory, $"{projectName.ToLowerInvariant()}.log");
logging.AddFile(
pathFormat: pathFormat,
fileSizeLimitBytes: loggingOptions.LogRollBySizeLimit.Value
);
}
else
{
var pathFormat = loggingOptions.LogDirectoryByProject
? Path.Combine(loggingOptions.LogDirectory, projectName, "{Date}.txt")
: Path.Combine(loggingOptions.LogDirectory, $"{projectName.ToLowerInvariant()}_{{Date}}.log");
logging.AddFile(
pathFormat: pathFormat
);
}
config.WriteTo.RollingFile(pathFormat);
}
config
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
});
}
var serilog = config.CreateLogger();
if (logSentryWarning)
{
serilog.Warning("Sentry for logging has been deprecated. Read more: https://btwrdn.com/log-deprecation");
}
if (logSyslogWarning)
{
serilog.Warning("Syslog for logging has been deprecated. Read more: https://btwrdn.com/log-deprecation");
}
builder.AddSerilog(serilog);
return builder;
/// <summary>
/// Our own proprietary options that we've always supported in `GlobalSettings` configuration section.
/// </summary>
private class LegacyFileLoggingOptions
{
public string? ProjectName { get; set; }
public string? LogDirectory { get; set; } = "/etc/bitwarden/logs";
public bool LogDirectoryByProject { get; set; } = true;
public long? LogRollBySizeLimit { get; set; }
}
}