2020-12-12 17:20:48 +01:00
#pragma warning disable CA1307
2020-05-13 04:10:35 +02:00
using System ;
2020-10-27 01:31:10 +01:00
using System.Collections.Concurrent ;
2020-05-13 04:10:35 +02:00
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2020-05-15 23:24:01 +02:00
using System.Text.RegularExpressions ;
2020-05-13 04:10:35 +02:00
using System.Threading.Tasks ;
2020-05-15 23:24:01 +02:00
using Jellyfin.Data.Entities ;
2020-05-13 04:10:35 +02:00
using Jellyfin.Data.Enums ;
2020-08-14 02:48:28 +02:00
using Jellyfin.Data.Events ;
2020-08-15 21:55:15 +02:00
using Jellyfin.Data.Events.Users ;
2020-05-19 21:58:25 +02:00
using MediaBrowser.Common ;
2020-06-25 02:36:58 +02:00
using MediaBrowser.Common.Extensions ;
2020-05-13 04:10:35 +02:00
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Authentication ;
2023-09-23 18:59:13 +02:00
using MediaBrowser.Controller.Configuration ;
2020-05-19 21:58:25 +02:00
using MediaBrowser.Controller.Drawing ;
2020-08-15 21:55:15 +02:00
using MediaBrowser.Controller.Events ;
2020-05-13 04:10:35 +02:00
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Net ;
using MediaBrowser.Model.Configuration ;
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Users ;
2020-07-12 20:45:52 +02:00
using Microsoft.EntityFrameworkCore ;
2020-05-13 04:10:35 +02:00
using Microsoft.Extensions.Logging ;
2020-05-15 23:24:01 +02:00
namespace Jellyfin.Server.Implementations.Users
2020-05-13 04:10:35 +02:00
{
2020-05-20 01:44:55 +02:00
/// <summary>
/// Manages the creation and retrieval of <see cref="User"/> instances.
/// </summary>
2023-05-22 22:48:09 +02:00
public partial class UserManager : IUserManager
2020-05-13 04:10:35 +02:00
{
2023-01-16 18:14:44 +01:00
private readonly IDbContextFactory < JellyfinDbContext > _dbProvider ;
2020-08-15 21:55:15 +02:00
private readonly IEventManager _eventManager ;
2020-05-13 04:10:35 +02:00
private readonly INetworkManager _networkManager ;
2020-05-19 21:58:25 +02:00
private readonly IApplicationHost _appHost ;
private readonly IImageProcessor _imageProcessor ;
2020-05-21 01:47:41 +02:00
private readonly ILogger < UserManager > _logger ;
2020-06-17 16:24:25 +02:00
private readonly IReadOnlyCollection < IPasswordResetProvider > _passwordResetProviders ;
private readonly IReadOnlyCollection < IAuthenticationProvider > _authenticationProviders ;
private readonly InvalidAuthProvider _invalidAuthProvider ;
private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider ;
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider ;
2023-09-23 18:59:13 +02:00
private readonly IServerConfigurationManager _serverConfigurationManager ;
2020-05-13 04:10:35 +02:00
2020-10-27 17:12:08 +01:00
private readonly IDictionary < Guid , User > _users ;
2020-10-27 01:31:10 +01:00
2020-05-20 01:44:55 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="UserManager"/> class.
/// </summary>
/// <param name="dbProvider">The database provider.</param>
2020-08-15 21:55:15 +02:00
/// <param name="eventManager">The event manager.</param>
2020-05-20 01:44:55 +02:00
/// <param name="networkManager">The network manager.</param>
/// <param name="appHost">The application host.</param>
/// <param name="imageProcessor">The image processor.</param>
/// <param name="logger">The logger.</param>
2023-09-23 18:59:13 +02:00
/// <param name="serverConfigurationManager">The system config manager.</param>
2020-05-13 04:10:35 +02:00
public UserManager (
2023-01-16 18:14:44 +01:00
IDbContextFactory < JellyfinDbContext > dbProvider ,
2020-08-15 21:55:15 +02:00
IEventManager eventManager ,
2020-05-13 04:10:35 +02:00
INetworkManager networkManager ,
2020-05-19 21:58:25 +02:00
IApplicationHost appHost ,
IImageProcessor imageProcessor ,
2023-09-23 18:59:13 +02:00
ILogger < UserManager > logger ,
IServerConfigurationManager serverConfigurationManager )
2020-05-13 04:10:35 +02:00
{
_dbProvider = dbProvider ;
2020-08-15 21:55:15 +02:00
_eventManager = eventManager ;
2020-05-13 04:10:35 +02:00
_networkManager = networkManager ;
2020-05-19 21:58:25 +02:00
_appHost = appHost ;
_imageProcessor = imageProcessor ;
2020-05-13 04:10:35 +02:00
_logger = logger ;
2023-09-23 18:59:13 +02:00
_serverConfigurationManager = serverConfigurationManager ;
2020-06-17 16:24:25 +02:00
2020-06-20 23:58:09 +02:00
_passwordResetProviders = appHost . GetExports < IPasswordResetProvider > ( ) ;
_authenticationProviders = appHost . GetExports < IAuthenticationProvider > ( ) ;
2020-06-17 16:24:25 +02:00
_invalidAuthProvider = _authenticationProviders . OfType < InvalidAuthProvider > ( ) . First ( ) ;
_defaultAuthenticationProvider = _authenticationProviders . OfType < DefaultAuthenticationProvider > ( ) . First ( ) ;
_defaultPasswordResetProvider = _passwordResetProviders . OfType < DefaultPasswordResetProvider > ( ) . First ( ) ;
2020-10-27 01:31:10 +01:00
2020-10-27 17:12:08 +01:00
_users = new ConcurrentDictionary < Guid , User > ( ) ;
2022-10-21 11:55:32 +02:00
using var dbContext = _dbProvider . CreateDbContext ( ) ;
2020-10-27 01:31:10 +01:00
foreach ( var user in dbContext . Users
2023-01-16 17:52:53 +01:00
. AsSplitQuery ( )
2020-10-27 01:31:10 +01:00
. Include ( user = > user . Permissions )
. Include ( user = > user . Preferences )
. Include ( user = > user . AccessSchedules )
. Include ( user = > user . ProfileImage )
. AsEnumerable ( ) )
{
_users . Add ( user . Id , user ) ;
}
2020-05-13 04:10:35 +02:00
}
/// <inheritdoc/>
2020-06-09 18:21:21 +02:00
public event EventHandler < GenericEventArgs < User > > ? OnUserUpdated ;
2020-05-13 04:10:35 +02:00
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-10-27 17:12:08 +01:00
public IEnumerable < User > Users = > _users . Values ;
2020-05-13 04:10:35 +02:00
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-10-27 17:12:08 +01:00
public IEnumerable < Guid > UsersIds = > _users . Keys ;
2020-05-13 04:10:35 +02:00
2023-05-22 22:48:09 +02:00
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
2023-10-08 00:26:12 +02:00
[GeneratedRegex(@"^[\w\ \-'._@] + $ ")]
2023-05-22 22:48:09 +02:00
private static partial Regex ValidUsernameRegex ( ) ;
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-06-09 18:21:21 +02:00
public User ? GetUserById ( Guid id )
2020-05-13 04:10:35 +02:00
{
2022-02-21 14:15:09 +01:00
if ( id . Equals ( default ) )
2020-05-13 04:10:35 +02:00
{
throw new ArgumentException ( "Guid can't be empty" , nameof ( id ) ) ;
}
2020-10-27 01:31:10 +01:00
_users . TryGetValue ( id , out var user ) ;
return user ;
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-06-09 18:21:21 +02:00
public User ? GetUserByName ( string name )
2020-05-13 04:10:35 +02:00
{
if ( string . IsNullOrWhiteSpace ( name ) )
{
throw new ArgumentException ( "Invalid username" , nameof ( name ) ) ;
}
2020-10-27 01:31:10 +01:00
return _users . Values . FirstOrDefault ( u = > string . Equals ( u . Username , name , StringComparison . OrdinalIgnoreCase ) ) ;
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-15 23:24:01 +02:00
public async Task RenameUser ( User user , string newName )
2020-05-13 04:10:35 +02:00
{
2022-10-06 20:21:23 +02:00
ArgumentNullException . ThrowIfNull ( user ) ;
2020-05-13 04:10:35 +02:00
2021-02-17 11:30:14 +01:00
ThrowIfInvalidUsername ( newName ) ;
2020-05-13 04:10:35 +02:00
2020-07-20 04:21:30 +02:00
if ( user . Username . Equals ( newName , StringComparison . Ordinal ) )
2020-05-13 04:10:35 +02:00
{
throw new ArgumentException ( "The new and old names must be different." ) ;
}
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
2020-05-13 04:10:35 +02:00
{
2022-10-21 11:55:32 +02:00
if ( await dbContext . Users
. AnyAsync ( u = > u . Username = = newName & & ! u . Id . Equals ( user . Id ) )
. ConfigureAwait ( false ) )
{
throw new ArgumentException ( string . Format (
CultureInfo . InvariantCulture ,
"A user with the name '{0}' already exists." ,
newName ) ) ;
}
user . Username = newName ;
await UpdateUserInternalAsync ( dbContext , user ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
}
2023-01-07 19:31:10 +01:00
var eventArgs = new UserUpdatedEventArgs ( user ) ;
await _eventManager . PublishAsync ( eventArgs ) . ConfigureAwait ( false ) ;
OnUserUpdated ? . Invoke ( this , eventArgs ) ;
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-15 23:24:01 +02:00
public async Task UpdateUserAsync ( User user )
2020-05-13 04:10:35 +02:00
{
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
await UpdateUserInternalAsync ( dbContext , user ) . ConfigureAwait ( false ) ;
}
2020-05-13 04:10:35 +02:00
}
2023-01-16 18:14:44 +01:00
internal async Task < User > CreateUserInternalAsync ( string name , JellyfinDbContext dbContext )
2020-07-22 20:57:29 +02:00
{
// TODO: Remove after user item data is migrated.
2020-10-06 04:51:52 +02:00
var max = await dbContext . Users . AsQueryable ( ) . AnyAsync ( ) . ConfigureAwait ( false )
? await dbContext . Users . AsQueryable ( ) . Select ( u = > u . InternalId ) . MaxAsync ( ) . ConfigureAwait ( false )
2020-07-22 20:57:29 +02:00
: 0 ;
2020-10-27 01:31:10 +01:00
var user = new User (
2020-07-22 20:57:29 +02:00
name ,
2021-03-06 23:43:01 +01:00
_defaultAuthenticationProvider . GetType ( ) . FullName ! ,
_defaultPasswordResetProvider . GetType ( ) . FullName ! )
2020-07-22 20:57:29 +02:00
{
InternalId = max + 1
} ;
2020-10-27 01:31:10 +01:00
2021-03-17 22:42:45 +01:00
user . AddDefaultPermissions ( ) ;
user . AddDefaultPreferences ( ) ;
2020-10-27 01:31:10 +01:00
_users . Add ( user . Id , user ) ;
return user ;
2020-07-22 20:57:29 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-07-22 20:57:29 +02:00
public async Task < User > CreateUserAsync ( string name )
2020-05-13 04:10:35 +02:00
{
2021-02-17 11:30:14 +01:00
ThrowIfInvalidUsername ( name ) ;
2020-05-15 23:24:01 +02:00
2021-02-17 02:48:41 +01:00
if ( Users . Any ( u = > u . Username . Equals ( name , StringComparison . OrdinalIgnoreCase ) ) )
{
throw new ArgumentException ( string . Format (
CultureInfo . InvariantCulture ,
"A user with the name '{0}' already exists." ,
name ) ) ;
}
2022-10-21 11:55:32 +02:00
User newUser ;
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
newUser = await CreateUserInternalAsync ( name , dbContext ) . ConfigureAwait ( false ) ;
2020-05-20 19:49:44 +02:00
2022-10-21 11:55:32 +02:00
dbContext . Users . Add ( newUser ) ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-05-13 04:10:35 +02:00
2020-08-15 21:55:15 +02:00
await _eventManager . PublishAsync ( new UserCreatedEventArgs ( newUser ) ) . ConfigureAwait ( false ) ;
2020-05-15 23:24:01 +02:00
2020-05-13 04:10:35 +02:00
return newUser ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-12-11 16:15:43 +01:00
public async Task DeleteUserAsync ( Guid userId )
2020-05-13 04:10:35 +02:00
{
2020-10-27 01:31:10 +01:00
if ( ! _users . TryGetValue ( userId , out var user ) )
2020-05-13 04:10:35 +02:00
{
2020-06-25 02:36:58 +02:00
throw new ResourceNotFoundException ( nameof ( userId ) ) ;
2020-05-13 04:10:35 +02:00
}
2020-10-27 01:31:10 +01:00
if ( _users . Count = = 1 )
2020-05-13 04:10:35 +02:00
{
throw new InvalidOperationException ( string . Format (
CultureInfo . InvariantCulture ,
"The user '{0}' cannot be deleted because there must be at least one user in the system." ,
user . Username ) ) ;
}
if ( user . HasPermission ( PermissionKind . IsAdministrator )
& & Users . Count ( i = > i . HasPermission ( PermissionKind . IsAdministrator ) ) = = 1 )
{
throw new ArgumentException (
string . Format (
CultureInfo . InvariantCulture ,
"The user '{0}' cannot be deleted because there must be at least one admin user in the system." ,
user . Username ) ,
2020-06-25 02:19:47 +02:00
nameof ( userId ) ) ;
}
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
dbContext . Users . Remove ( user ) ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-10-27 01:31:10 +01:00
_users . Remove ( userId ) ;
2020-08-15 21:55:15 +02:00
2020-12-11 16:15:43 +01:00
await _eventManager . PublishAsync ( new UserDeletedEventArgs ( user ) ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-15 23:24:01 +02:00
public Task ResetPassword ( User user )
2020-05-13 04:10:35 +02:00
{
return ChangePassword ( user , string . Empty ) ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-15 23:24:01 +02:00
public async Task ChangePassword ( User user , string newPassword )
2020-05-13 04:10:35 +02:00
{
2022-10-06 20:21:23 +02:00
ArgumentNullException . ThrowIfNull ( user ) ;
2023-05-26 19:52:27 +02:00
if ( user . HasPermission ( PermissionKind . IsAdministrator ) & & string . IsNullOrWhiteSpace ( newPassword ) )
{
throw new ArgumentException ( "Admin user passwords must not be empty" , nameof ( newPassword ) ) ;
}
2020-05-13 04:10:35 +02:00
await GetAuthenticationProvider ( user ) . ChangePassword ( user , newPassword ) . ConfigureAwait ( false ) ;
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
2020-08-15 21:55:15 +02:00
await _eventManager . PublishAsync ( new UserPasswordChangedEventArgs ( user ) ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-06-09 18:21:21 +02:00
public UserDto GetUserDto ( User user , string? remoteEndPoint = null )
2020-05-13 04:10:35 +02:00
{
2020-06-08 05:11:51 +02:00
var hasPassword = GetAuthenticationProvider ( user ) . HasPassword ( user ) ;
2023-10-14 18:55:12 +02:00
var castReceiverApplications = _serverConfigurationManager . Configuration . CastReceiverApplications ;
2020-05-13 04:10:35 +02:00
return new UserDto
{
2020-05-19 21:58:25 +02:00
Name = user . Username ,
2020-05-13 04:10:35 +02:00
Id = user . Id ,
2020-05-19 21:58:25 +02:00
ServerId = _appHost . SystemId ,
2020-06-08 05:11:51 +02:00
HasPassword = hasPassword ,
HasConfiguredPassword = hasPassword ,
2020-05-13 04:10:35 +02:00
EnableAutoLogin = user . EnableAutoLogin ,
LastLoginDate = user . LastLoginDate ,
LastActivityDate = user . LastActivityDate ,
2022-12-05 15:01:13 +01:00
PrimaryImageTag = user . ProfileImage is not null ? _imageProcessor . GetImageCacheTag ( user ) : null ,
2020-05-13 04:10:35 +02:00
Configuration = new UserConfiguration
{
SubtitleMode = user . SubtitleMode ,
HidePlayedInLatest = user . HidePlayedInLatest ,
EnableLocalPassword = user . EnableLocalPassword ,
PlayDefaultAudioTrack = user . PlayDefaultAudioTrack ,
DisplayCollectionsView = user . DisplayCollectionsView ,
DisplayMissingEpisodes = user . DisplayMissingEpisodes ,
AudioLanguagePreference = user . AudioLanguagePreference ,
RememberAudioSelections = user . RememberAudioSelections ,
EnableNextEpisodeAutoPlay = user . EnableNextEpisodeAutoPlay ,
RememberSubtitleSelections = user . RememberSubtitleSelections ,
2020-05-19 21:58:25 +02:00
SubtitleLanguagePreference = user . SubtitleLanguagePreference ? ? string . Empty ,
2022-08-27 14:19:47 +02:00
OrderedViews = user . GetPreferenceValues < Guid > ( PreferenceKind . OrderedViews ) ,
GroupedFolders = user . GetPreferenceValues < Guid > ( PreferenceKind . GroupedFolders ) ,
MyMediaExcludes = user . GetPreferenceValues < Guid > ( PreferenceKind . MyMediaExcludes ) ,
2023-09-23 18:59:13 +02:00
LatestItemsExcludes = user . GetPreferenceValues < Guid > ( PreferenceKind . LatestItemExcludes ) ,
CastReceiverId = string . IsNullOrEmpty ( user . CastReceiverId )
2023-10-14 18:55:12 +02:00
? castReceiverApplications . FirstOrDefault ( ) ? . Id
: castReceiverApplications . FirstOrDefault ( c = > string . Equals ( c . Id , user . CastReceiverId , StringComparison . Ordinal ) ) ? . Id
? ? castReceiverApplications . FirstOrDefault ( ) ? . Id
2020-05-13 04:10:35 +02:00
} ,
Policy = new UserPolicy
{
MaxParentalRating = user . MaxParentalAgeRating ,
EnableUserPreferenceAccess = user . EnableUserPreferenceAccess ,
2020-06-08 01:37:47 +02:00
RemoteClientBitrateLimit = user . RemoteClientBitrateLimit ? ? 0 ,
2020-05-15 23:24:01 +02:00
AuthenticationProviderId = user . AuthenticationProviderId ,
2020-05-13 04:10:35 +02:00
PasswordResetProviderId = user . PasswordResetProviderId ,
InvalidLoginAttemptCount = user . InvalidLoginAttemptCount ,
2020-05-28 07:08:37 +02:00
LoginAttemptsBeforeLockout = user . LoginAttemptsBeforeLockout ? ? - 1 ,
2020-10-04 19:34:53 +02:00
MaxActiveSessions = user . MaxActiveSessions ,
2020-05-13 04:10:35 +02:00
IsAdministrator = user . HasPermission ( PermissionKind . IsAdministrator ) ,
IsHidden = user . HasPermission ( PermissionKind . IsHidden ) ,
IsDisabled = user . HasPermission ( PermissionKind . IsDisabled ) ,
EnableSharedDeviceControl = user . HasPermission ( PermissionKind . EnableSharedDeviceControl ) ,
EnableRemoteAccess = user . HasPermission ( PermissionKind . EnableRemoteAccess ) ,
EnableLiveTvManagement = user . HasPermission ( PermissionKind . EnableLiveTvManagement ) ,
EnableLiveTvAccess = user . HasPermission ( PermissionKind . EnableLiveTvAccess ) ,
EnableMediaPlayback = user . HasPermission ( PermissionKind . EnableMediaPlayback ) ,
EnableAudioPlaybackTranscoding = user . HasPermission ( PermissionKind . EnableAudioPlaybackTranscoding ) ,
EnableVideoPlaybackTranscoding = user . HasPermission ( PermissionKind . EnableVideoPlaybackTranscoding ) ,
EnableContentDeletion = user . HasPermission ( PermissionKind . EnableContentDeletion ) ,
EnableContentDownloading = user . HasPermission ( PermissionKind . EnableContentDownloading ) ,
EnableSyncTranscoding = user . HasPermission ( PermissionKind . EnableSyncTranscoding ) ,
EnableMediaConversion = user . HasPermission ( PermissionKind . EnableMediaConversion ) ,
EnableAllChannels = user . HasPermission ( PermissionKind . EnableAllChannels ) ,
EnableAllDevices = user . HasPermission ( PermissionKind . EnableAllDevices ) ,
EnableAllFolders = user . HasPermission ( PermissionKind . EnableAllFolders ) ,
EnableRemoteControlOfOtherUsers = user . HasPermission ( PermissionKind . EnableRemoteControlOfOtherUsers ) ,
EnablePlaybackRemuxing = user . HasPermission ( PermissionKind . EnablePlaybackRemuxing ) ,
ForceRemoteSourceTranscoding = user . HasPermission ( PermissionKind . ForceRemoteSourceTranscoding ) ,
EnablePublicSharing = user . HasPermission ( PermissionKind . EnablePublicSharing ) ,
2023-02-13 15:42:04 +01:00
EnableCollectionManagement = user . HasPermission ( PermissionKind . EnableCollectionManagement ) ,
2020-05-13 04:10:35 +02:00
AccessSchedules = user . AccessSchedules . ToArray ( ) ,
BlockedTags = user . GetPreference ( PreferenceKind . BlockedTags ) ,
2023-01-20 03:28:52 +01:00
AllowedTags = user . GetPreference ( PreferenceKind . AllowedTags ) ,
2020-12-13 16:15:26 +01:00
EnabledChannels = user . GetPreferenceValues < Guid > ( PreferenceKind . EnabledChannels ) ,
2020-05-13 04:10:35 +02:00
EnabledDevices = user . GetPreference ( PreferenceKind . EnabledDevices ) ,
2020-12-13 16:15:26 +01:00
EnabledFolders = user . GetPreferenceValues < Guid > ( PreferenceKind . EnabledFolders ) ,
2020-05-27 02:52:05 +02:00
EnableContentDeletionFromFolders = user . GetPreference ( PreferenceKind . EnableContentDeletionFromFolders ) ,
2020-06-14 00:26:46 +02:00
SyncPlayAccess = user . SyncPlayAccess ,
2020-12-13 16:15:26 +01:00
BlockedChannels = user . GetPreferenceValues < Guid > ( PreferenceKind . BlockedChannels ) ,
BlockedMediaFolders = user . GetPreferenceValues < Guid > ( PreferenceKind . BlockedMediaFolders ) ,
BlockUnratedItems = user . GetPreferenceValues < UnratedItem > ( PreferenceKind . BlockUnratedItems )
2020-05-13 04:10:35 +02:00
}
} ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-06-09 18:21:21 +02:00
public async Task < User ? > AuthenticateUser (
2020-05-13 04:10:35 +02:00
string username ,
string password ,
string passwordSha1 ,
string remoteEndPoint ,
bool isUserSession )
{
if ( string . IsNullOrWhiteSpace ( username ) )
{
_logger . LogInformation ( "Authentication request without username has been denied (IP: {IP})." , remoteEndPoint ) ;
throw new ArgumentNullException ( nameof ( username ) ) ;
}
2020-07-12 20:45:52 +02:00
var user = Users . FirstOrDefault ( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
2023-10-03 15:26:20 +02:00
var authResult = await AuthenticateLocalUser ( username , password , user )
2021-02-06 21:59:27 +01:00
. ConfigureAwait ( false ) ;
2021-12-24 22:18:24 +01:00
var authenticationProvider = authResult . AuthenticationProvider ;
var success = authResult . Success ;
2020-05-13 04:10:35 +02:00
2022-12-05 15:00:20 +01:00
if ( user is null )
2020-05-13 04:10:35 +02:00
{
2021-12-24 22:18:24 +01:00
string updatedUsername = authResult . Username ;
2020-05-13 04:10:35 +02:00
if ( success
2022-12-05 15:01:13 +01:00
& & authenticationProvider is not null
2021-02-06 21:59:27 +01:00
& & authenticationProvider is not DefaultAuthenticationProvider )
2020-05-13 04:10:35 +02:00
{
// Trust the username returned by the authentication provider
username = updatedUsername ;
// Search the database for the user again
// the authentication provider might have created it
2020-07-12 20:45:52 +02:00
user = Users . FirstOrDefault ( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
2020-05-13 04:10:35 +02:00
2022-12-05 15:01:13 +01:00
if ( authenticationProvider is IHasNewUserPolicy hasNewUserPolicy & & user is not null )
2020-05-13 04:10:35 +02:00
{
2020-10-30 01:30:33 +01:00
await UpdatePolicyAsync ( user . Id , hasNewUserPolicy . GetNewUserPolicy ( ) ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
}
}
}
2022-12-05 15:01:13 +01:00
if ( success & & user is not null & & authenticationProvider is not null )
2020-05-13 04:10:35 +02:00
{
var providerId = authenticationProvider . GetType ( ) . FullName ;
2022-12-05 15:01:13 +01:00
if ( providerId is not null & & ! string . Equals ( providerId , user . AuthenticationProviderId , StringComparison . OrdinalIgnoreCase ) )
2020-05-13 04:10:35 +02:00
{
user . AuthenticationProviderId = providerId ;
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
}
}
2022-12-05 15:00:20 +01:00
if ( user is null )
2020-05-13 04:10:35 +02:00
{
_logger . LogInformation (
"Authentication request for {UserName} has been denied (IP: {IP})." ,
username ,
remoteEndPoint ) ;
throw new AuthenticationException ( "Invalid username or password entered." ) ;
}
if ( user . HasPermission ( PermissionKind . IsDisabled ) )
{
_logger . LogInformation (
"Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP})." ,
username ,
remoteEndPoint ) ;
throw new SecurityException (
$"The {user.Username} account is currently disabled. Please consult with your administrator." ) ;
}
if ( ! user . HasPermission ( PermissionKind . EnableRemoteAccess ) & &
! _networkManager . IsInLocalNetwork ( remoteEndPoint ) )
{
_logger . LogInformation (
"Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP})." ,
username ,
remoteEndPoint ) ;
throw new SecurityException ( "Forbidden." ) ;
}
if ( ! user . IsParentalScheduleAllowed ( ) )
{
_logger . LogInformation (
"Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP})." ,
username ,
remoteEndPoint ) ;
throw new SecurityException ( "User is not allowed access at this time." ) ;
}
// Update LastActivityDate and LastLoginDate, then save
if ( success )
{
if ( isUserSession )
{
user . LastActivityDate = user . LastLoginDate = DateTime . UtcNow ;
}
2020-05-15 23:24:01 +02:00
user . InvalidLoginAttemptCount = 0 ;
2020-05-30 06:20:59 +02:00
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
_logger . LogInformation ( "Authentication request for {UserName} has succeeded." , user . Username ) ;
}
else
{
2020-07-21 20:25:52 +02:00
await IncrementInvalidLoginAttemptCount ( user ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
_logger . LogInformation (
"Authentication request for {UserName} has been denied (IP: {IP})." ,
user . Username ,
remoteEndPoint ) ;
}
return success ? user : null ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-13 04:10:35 +02:00
public async Task < ForgotPasswordResult > StartForgotPasswordProcess ( string enteredUsername , bool isInNetwork )
{
var user = string . IsNullOrWhiteSpace ( enteredUsername ) ? null : GetUserByName ( enteredUsername ) ;
2022-12-05 15:01:13 +01:00
if ( user is not null & & isInNetwork )
2020-05-13 04:10:35 +02:00
{
var passwordResetProvider = GetPasswordResetProvider ( user ) ;
2020-06-20 23:58:09 +02:00
var result = await passwordResetProvider
. StartForgotPasswordProcess ( user , isInNetwork )
. ConfigureAwait ( false ) ;
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
return result ;
2020-05-13 04:10:35 +02:00
}
return new ForgotPasswordResult
{
2020-05-28 07:08:37 +02:00
Action = ForgotPasswordAction . InNetworkRequired ,
2020-05-13 04:10:35 +02:00
PinFile = string . Empty
} ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-13 04:10:35 +02:00
public async Task < PinRedeemResult > RedeemPasswordResetPin ( string pin )
{
foreach ( var provider in _passwordResetProviders )
{
var result = await provider . RedeemPasswordResetPin ( pin ) . ConfigureAwait ( false ) ;
if ( result . Success )
{
return result ;
}
}
2021-10-26 13:56:30 +02:00
return new PinRedeemResult ( ) ;
2020-05-13 04:10:35 +02:00
}
2020-06-09 20:01:21 +02:00
/// <inheritdoc />
2020-07-22 20:57:29 +02:00
public async Task InitializeAsync ( )
2020-06-09 20:01:21 +02:00
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
2020-10-27 01:31:10 +01:00
if ( _users . Any ( ) )
2020-06-09 20:01:21 +02:00
{
return ;
}
var defaultName = Environment . UserName ;
2023-05-22 22:48:09 +02:00
if ( string . IsNullOrWhiteSpace ( defaultName ) | | ! ValidUsernameRegex ( ) . IsMatch ( defaultName ) )
2020-06-09 20:01:21 +02:00
{
defaultName = "MyJellyfinUser" ;
}
_logger . LogWarning ( "No users, creating one with username {UserName}" , defaultName ) ;
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
var newUser = await CreateUserInternalAsync ( defaultName , dbContext ) . ConfigureAwait ( false ) ;
newUser . SetPermission ( PermissionKind . IsAdministrator , true ) ;
newUser . SetPermission ( PermissionKind . EnableContentDeletion , true ) ;
newUser . SetPermission ( PermissionKind . EnableRemoteControlOfOtherUsers , true ) ;
2020-06-09 20:01:21 +02:00
2022-10-21 11:55:32 +02:00
dbContext . Users . Add ( newUser ) ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-06-09 20:01:21 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-13 04:10:35 +02:00
public NameIdPair [ ] GetAuthenticationProviders ( )
{
return _authenticationProviders
. Where ( provider = > provider . IsEnabled )
. OrderBy ( i = > i is DefaultAuthenticationProvider ? 0 : 1 )
. ThenBy ( i = > i . Name )
. Select ( i = > new NameIdPair
{
Name = i . Name ,
Id = i . GetType ( ) . FullName
} )
. ToArray ( ) ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-05-13 04:10:35 +02:00
public NameIdPair [ ] GetPasswordResetProviders ( )
{
return _passwordResetProviders
. Where ( provider = > provider . IsEnabled )
. OrderBy ( i = > i is DefaultPasswordResetProvider ? 0 : 1 )
. ThenBy ( i = > i . Name )
. Select ( i = > new NameIdPair
{
Name = i . Name ,
Id = i . GetType ( ) . FullName
} )
. ToArray ( ) ;
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-10-30 01:30:33 +01:00
public async Task UpdateConfigurationAsync ( Guid userId , UserConfiguration config )
2020-05-13 04:10:35 +02:00
{
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
var user = dbContext . Users
. Include ( u = > u . Permissions )
. Include ( u = > u . Preferences )
. Include ( u = > u . AccessSchedules )
. Include ( u = > u . ProfileImage )
. FirstOrDefault ( u = > u . Id . Equals ( userId ) )
? ? throw new ArgumentException ( "No user exists with given Id!" ) ;
user . SubtitleMode = config . SubtitleMode ;
user . HidePlayedInLatest = config . HidePlayedInLatest ;
user . EnableLocalPassword = config . EnableLocalPassword ;
user . PlayDefaultAudioTrack = config . PlayDefaultAudioTrack ;
user . DisplayCollectionsView = config . DisplayCollectionsView ;
user . DisplayMissingEpisodes = config . DisplayMissingEpisodes ;
user . AudioLanguagePreference = config . AudioLanguagePreference ;
user . RememberAudioSelections = config . RememberAudioSelections ;
user . EnableNextEpisodeAutoPlay = config . EnableNextEpisodeAutoPlay ;
user . RememberSubtitleSelections = config . RememberSubtitleSelections ;
user . SubtitleLanguagePreference = config . SubtitleLanguagePreference ;
2023-09-24 00:14:03 +02:00
// Only set cast receiver id if it is passed in and it exists in the server config.
if ( ! string . IsNullOrEmpty ( config . CastReceiverId )
& & _serverConfigurationManager . Configuration . CastReceiverApplications . Any ( c = > string . Equals ( c . Id , config . CastReceiverId , StringComparison . Ordinal ) ) )
2023-09-23 18:59:13 +02:00
{
user . CastReceiverId = config . CastReceiverId ;
}
2022-10-21 11:55:32 +02:00
user . SetPreference ( PreferenceKind . OrderedViews , config . OrderedViews ) ;
user . SetPreference ( PreferenceKind . GroupedFolders , config . GroupedFolders ) ;
user . SetPreference ( PreferenceKind . MyMediaExcludes , config . MyMediaExcludes ) ;
user . SetPreference ( PreferenceKind . LatestItemExcludes , config . LatestItemsExcludes ) ;
dbContext . Update ( user ) ;
_users [ user . Id ] = user ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-05-13 04:10:35 +02:00
}
2020-05-20 01:44:55 +02:00
/// <inheritdoc/>
2020-10-30 01:30:33 +01:00
public async Task UpdatePolicyAsync ( Guid userId , UserPolicy policy )
2020-05-13 04:10:35 +02:00
{
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
var user = dbContext . Users
. Include ( u = > u . Permissions )
. Include ( u = > u . Preferences )
. Include ( u = > u . AccessSchedules )
. Include ( u = > u . ProfileImage )
. FirstOrDefault ( u = > u . Id . Equals ( userId ) )
? ? throw new ArgumentException ( "No user exists with given Id!" ) ;
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
int? maxLoginAttempts = policy . LoginAttemptsBeforeLockout switch
{
- 1 = > null ,
0 = > 3 ,
_ = > policy . LoginAttemptsBeforeLockout
} ;
2020-05-13 04:10:35 +02:00
2022-10-21 11:55:32 +02:00
user . MaxParentalAgeRating = policy . MaxParentalRating ;
user . EnableUserPreferenceAccess = policy . EnableUserPreferenceAccess ;
user . RemoteClientBitrateLimit = policy . RemoteClientBitrateLimit ;
user . AuthenticationProviderId = policy . AuthenticationProviderId ;
user . PasswordResetProviderId = policy . PasswordResetProviderId ;
user . InvalidLoginAttemptCount = policy . InvalidLoginAttemptCount ;
user . LoginAttemptsBeforeLockout = maxLoginAttempts ;
user . MaxActiveSessions = policy . MaxActiveSessions ;
user . SyncPlayAccess = policy . SyncPlayAccess ;
user . SetPermission ( PermissionKind . IsAdministrator , policy . IsAdministrator ) ;
user . SetPermission ( PermissionKind . IsHidden , policy . IsHidden ) ;
user . SetPermission ( PermissionKind . IsDisabled , policy . IsDisabled ) ;
user . SetPermission ( PermissionKind . EnableSharedDeviceControl , policy . EnableSharedDeviceControl ) ;
user . SetPermission ( PermissionKind . EnableRemoteAccess , policy . EnableRemoteAccess ) ;
user . SetPermission ( PermissionKind . EnableLiveTvManagement , policy . EnableLiveTvManagement ) ;
user . SetPermission ( PermissionKind . EnableLiveTvAccess , policy . EnableLiveTvAccess ) ;
user . SetPermission ( PermissionKind . EnableMediaPlayback , policy . EnableMediaPlayback ) ;
user . SetPermission ( PermissionKind . EnableAudioPlaybackTranscoding , policy . EnableAudioPlaybackTranscoding ) ;
user . SetPermission ( PermissionKind . EnableVideoPlaybackTranscoding , policy . EnableVideoPlaybackTranscoding ) ;
user . SetPermission ( PermissionKind . EnableContentDeletion , policy . EnableContentDeletion ) ;
user . SetPermission ( PermissionKind . EnableContentDownloading , policy . EnableContentDownloading ) ;
user . SetPermission ( PermissionKind . EnableSyncTranscoding , policy . EnableSyncTranscoding ) ;
user . SetPermission ( PermissionKind . EnableMediaConversion , policy . EnableMediaConversion ) ;
user . SetPermission ( PermissionKind . EnableAllChannels , policy . EnableAllChannels ) ;
user . SetPermission ( PermissionKind . EnableAllDevices , policy . EnableAllDevices ) ;
user . SetPermission ( PermissionKind . EnableAllFolders , policy . EnableAllFolders ) ;
user . SetPermission ( PermissionKind . EnableRemoteControlOfOtherUsers , policy . EnableRemoteControlOfOtherUsers ) ;
user . SetPermission ( PermissionKind . EnablePlaybackRemuxing , policy . EnablePlaybackRemuxing ) ;
2023-02-13 15:42:04 +01:00
user . SetPermission ( PermissionKind . EnableCollectionManagement , policy . EnableCollectionManagement ) ;
2022-10-21 11:55:32 +02:00
user . SetPermission ( PermissionKind . ForceRemoteSourceTranscoding , policy . ForceRemoteSourceTranscoding ) ;
user . SetPermission ( PermissionKind . EnablePublicSharing , policy . EnablePublicSharing ) ;
user . AccessSchedules . Clear ( ) ;
foreach ( var policyAccessSchedule in policy . AccessSchedules )
{
user . AccessSchedules . Add ( policyAccessSchedule ) ;
}
// TODO: fix this at some point
user . SetPreference ( PreferenceKind . BlockUnratedItems , policy . BlockUnratedItems ? ? Array . Empty < UnratedItem > ( ) ) ;
user . SetPreference ( PreferenceKind . BlockedTags , policy . BlockedTags ) ;
2023-01-20 03:28:52 +01:00
user . SetPreference ( PreferenceKind . AllowedTags , policy . AllowedTags ) ;
2022-10-21 11:55:32 +02:00
user . SetPreference ( PreferenceKind . EnabledChannels , policy . EnabledChannels ) ;
user . SetPreference ( PreferenceKind . EnabledDevices , policy . EnabledDevices ) ;
user . SetPreference ( PreferenceKind . EnabledFolders , policy . EnabledFolders ) ;
user . SetPreference ( PreferenceKind . EnableContentDeletionFromFolders , policy . EnableContentDeletionFromFolders ) ;
dbContext . Update ( user ) ;
_users [ user . Id ] = user ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-05-13 04:10:35 +02:00
}
2020-06-12 00:28:49 +02:00
/// <inheritdoc/>
2020-10-30 01:30:33 +01:00
public async Task ClearProfileImageAsync ( User user )
2020-06-11 23:51:02 +02:00
{
2022-12-05 15:00:20 +01:00
if ( user . ProfileImage is null )
2021-10-04 15:43:40 +02:00
{
return ;
}
2022-10-21 11:55:32 +02:00
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
dbContext . Remove ( user . ProfileImage ) ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-10-27 17:28:37 +01:00
user . ProfileImage = null ;
2020-11-15 17:30:04 +01:00
_users [ user . Id ] = user ;
2020-06-11 23:51:02 +02:00
}
2021-02-17 11:30:14 +01:00
internal static void ThrowIfInvalidUsername ( string name )
{
2023-05-22 22:48:09 +02:00
if ( ! string . IsNullOrWhiteSpace ( name ) & & ValidUsernameRegex ( ) . IsMatch ( name ) )
2021-02-17 11:30:14 +01:00
{
return ;
}
throw new ArgumentException ( "Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)" , nameof ( name ) ) ;
}
2020-05-15 23:24:01 +02:00
private IAuthenticationProvider GetAuthenticationProvider ( User user )
2020-05-13 04:10:35 +02:00
{
return GetAuthenticationProviders ( user ) [ 0 ] ;
}
2020-05-15 23:24:01 +02:00
private IPasswordResetProvider GetPasswordResetProvider ( User user )
2020-05-13 04:10:35 +02:00
{
return GetPasswordResetProviders ( user ) [ 0 ] ;
}
2020-06-09 18:21:21 +02:00
private IList < IAuthenticationProvider > GetAuthenticationProviders ( User ? user )
2020-05-13 04:10:35 +02:00
{
var authenticationProviderId = user ? . AuthenticationProviderId ;
var providers = _authenticationProviders . Where ( i = > i . IsEnabled ) . ToList ( ) ;
if ( ! string . IsNullOrEmpty ( authenticationProviderId ) )
{
providers = providers . Where ( i = > string . Equals ( authenticationProviderId , i . GetType ( ) . FullName , StringComparison . OrdinalIgnoreCase ) ) . ToList ( ) ;
}
if ( providers . Count = = 0 )
{
// Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
_logger . LogWarning (
2020-05-15 23:24:01 +02:00
"User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected" ,
2020-05-13 04:10:35 +02:00
user ? . Username ,
user ? . AuthenticationProviderId ) ;
providers = new List < IAuthenticationProvider >
{
_invalidAuthProvider
} ;
}
return providers ;
}
2020-05-15 23:24:01 +02:00
private IList < IPasswordResetProvider > GetPasswordResetProviders ( User user )
2020-05-13 04:10:35 +02:00
{
2020-10-03 17:03:23 +02:00
var passwordResetProviderId = user . PasswordResetProviderId ;
2020-05-13 04:10:35 +02:00
var providers = _passwordResetProviders . Where ( i = > i . IsEnabled ) . ToArray ( ) ;
if ( ! string . IsNullOrEmpty ( passwordResetProviderId ) )
{
providers = providers . Where ( i = >
string . Equals ( passwordResetProviderId , i . GetType ( ) . FullName , StringComparison . OrdinalIgnoreCase ) )
. ToArray ( ) ;
}
if ( providers . Length = = 0 )
{
providers = new IPasswordResetProvider [ ]
{
_defaultPasswordResetProvider
} ;
}
return providers ;
}
2021-12-24 22:18:24 +01:00
private async Task < ( IAuthenticationProvider ? AuthenticationProvider , string Username , bool Success ) > AuthenticateLocalUser (
2020-05-13 04:10:35 +02:00
string username ,
string password ,
2023-10-03 15:26:20 +02:00
User ? user )
2020-05-13 04:10:35 +02:00
{
bool success = false ;
2020-06-09 18:21:21 +02:00
IAuthenticationProvider ? authenticationProvider = null ;
2020-05-13 04:10:35 +02:00
foreach ( var provider in GetAuthenticationProviders ( user ) )
{
var providerAuthResult =
await AuthenticateWithProvider ( provider , username , password , user ) . ConfigureAwait ( false ) ;
2021-12-24 22:18:24 +01:00
var updatedUsername = providerAuthResult . Username ;
success = providerAuthResult . Success ;
2020-05-13 04:10:35 +02:00
if ( success )
{
authenticationProvider = provider ;
username = updatedUsername ;
break ;
}
}
return ( authenticationProvider , username , success ) ;
}
2021-12-24 22:18:24 +01:00
private async Task < ( string Username , bool Success ) > AuthenticateWithProvider (
2020-05-13 04:10:35 +02:00
IAuthenticationProvider provider ,
string username ,
string password ,
2020-06-09 18:21:21 +02:00
User ? resolvedUser )
2020-05-13 04:10:35 +02:00
{
try
{
var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
? await requiresResolvedUser . Authenticate ( username , password , resolvedUser ) . ConfigureAwait ( false )
: await provider . Authenticate ( username , password ) . ConfigureAwait ( false ) ;
if ( authenticationResult . Username ! = username )
{
_logger . LogDebug ( "Authentication provider provided updated username {1}" , authenticationResult . Username ) ;
username = authenticationResult . Username ;
}
return ( username , true ) ;
}
catch ( AuthenticationException ex )
{
2023-08-21 19:09:32 +02:00
_logger . LogDebug ( ex , "Error authenticating with provider {Provider}" , provider . Name ) ;
2020-05-13 04:10:35 +02:00
return ( username , false ) ;
}
}
2020-07-21 20:25:52 +02:00
private async Task IncrementInvalidLoginAttemptCount ( User user )
2020-05-13 04:10:35 +02:00
{
2020-05-23 03:45:31 +02:00
user . InvalidLoginAttemptCount + + ;
2020-05-13 04:10:35 +02:00
int? maxInvalidLogins = user . LoginAttemptsBeforeLockout ;
2020-05-23 03:45:31 +02:00
if ( maxInvalidLogins . HasValue & & user . InvalidLoginAttemptCount > = maxInvalidLogins )
2020-05-13 04:10:35 +02:00
{
user . SetPermission ( PermissionKind . IsDisabled , true ) ;
2020-08-15 21:55:15 +02:00
await _eventManager . PublishAsync ( new UserLockedOutEventArgs ( user ) ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
_logger . LogWarning (
2020-05-15 23:24:01 +02:00
"Disabling user {Username} due to {Attempts} unsuccessful login attempts." ,
2020-05-13 04:10:35 +02:00
user . Username ,
2020-05-23 03:45:31 +02:00
user . InvalidLoginAttemptCount ) ;
2020-05-13 04:10:35 +02:00
}
2020-07-21 20:25:52 +02:00
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
2020-05-13 04:10:35 +02:00
}
2022-10-21 11:55:32 +02:00
2023-01-16 18:14:44 +01:00
private async Task UpdateUserInternalAsync ( JellyfinDbContext dbContext , User user )
2022-10-21 11:55:32 +02:00
{
dbContext . Users . Update ( user ) ;
_users [ user . Id ] = user ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2020-05-13 04:10:35 +02:00
}
}