@ -1,4 +1,4 @@
#pragma warning disable C S006 7
#pragma warning disable C A130 7
#pragma warning disable CS1591
using System ;
@ -6,7 +6,9 @@ using System.Collections.Generic;
using System.Globalization ;
using System.Linq ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using Jellyfin.Data.Entities ;
using Jellyfin.Data.Enums ;
using MediaBrowser.Common.Cryptography ;
using MediaBrowser.Common.Net ;
@ -20,7 +22,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Users ;
using Microsoft.Extensions.Logging ;
namespace Jellyfin.Server.Implementations.User
namespace Jellyfin.Server.Implementations.User s
{
public class UserManager : IUserManager
{
@ -47,24 +49,24 @@ namespace Jellyfin.Server.Implementations.User
_logger = logger ;
}
public event EventHandler < GenericEventArgs < Data. Entities . User> > OnUserPasswordChanged ;
public event EventHandler < GenericEventArgs < User> > OnUserPasswordChanged ;
/// <inheritdoc/>
public event EventHandler < GenericEventArgs < Data. Entities . User> > OnUserUpdated ;
public event EventHandler < GenericEventArgs < User> > OnUserUpdated ;
/// <inheritdoc/>
public event EventHandler < GenericEventArgs < Data. Entities . User> > OnUserCreated ;
public event EventHandler < GenericEventArgs < User> > OnUserCreated ;
/// <inheritdoc/>
public event EventHandler < GenericEventArgs < Data. Entities . User> > OnUserDeleted ;
public event EventHandler < GenericEventArgs < User> > OnUserDeleted ;
public event EventHandler < GenericEventArgs < Data. Entities . User> > OnUserLockedOut ;
public event EventHandler < GenericEventArgs < User> > OnUserLockedOut ;
public IEnumerable < Data. Entities . User> Users
public IEnumerable < User> Users
{
get
{
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
return dbContext . Users ;
}
}
@ -73,37 +75,38 @@ namespace Jellyfin.Server.Implementations.User
{
get
{
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
return dbContext . Users . Select ( u = > u . Id ) ;
}
}
public Data. Entities . User GetUserById ( Guid id )
public User GetUserById ( Guid id )
{
if ( id = = Guid . Empty )
{
throw new ArgumentException ( "Guid can't be empty" , nameof ( id ) ) ;
}
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
return dbContext . Users . Find ( id ) ;
}
public Data. Entities . User GetUserByName ( string name )
public User GetUserByName ( string name )
{
if ( string . IsNullOrWhiteSpace ( name ) )
{
throw new ArgumentException ( "Invalid username" , nameof ( name ) ) ;
}
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
return dbContext . Users . FirstOrDefault ( u = >
string . Equals ( u . Username , name , StringComparison . OrdinalIgnoreCase ) ) ;
// This can't use an overload with StringComparer because that would cause the query to
// have to be evaluated client-side.
return dbContext . Users . FirstOrDefault ( u = > string . Equals ( u . Username , name ) ) ;
}
public async Task RenameUser ( Data. Entities . User user , string newName )
public async Task RenameUser ( User user , string newName )
{
if ( user = = null )
{
@ -132,43 +135,50 @@ namespace Jellyfin.Server.Implementations.User
user . Username = newName ;
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
OnUserUpdated ? . Invoke ( this , new GenericEventArgs < Data. Entities . User> ( user ) ) ;
OnUserUpdated ? . Invoke ( this , new GenericEventArgs < User> ( user ) ) ;
}
public void UpdateUser ( Data. Entities . User user )
public void UpdateUser ( User user )
{
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
dbContext . Users . Update ( user ) ;
dbContext . SaveChanges ( ) ;
}
public async Task UpdateUserAsync ( Data. Entities . User user )
public async Task UpdateUserAsync ( User user )
{
await using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
dbContext . Users . Update ( user ) ;
await dbContext . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
public Data. Entities . User CreateUser ( string name )
public User CreateUser ( string name )
{
using var dbContext = _dbProvider . CreateContext ( ) ;
if ( ! IsValidUsername ( name ) )
{
throw new ArgumentException ( "Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)" ) ;
}
var dbContext = _dbProvider . CreateContext ( ) ;
var newUser = CreateUserObject ( name ) ;
var newUser = new User ( name , _defaultAuthenticationProvider . GetType ( ) . FullN ame) ;
dbContext . Users . Add ( newUser ) ;
dbContext . SaveChanges ( ) ;
OnUserCreated ? . Invoke ( this , new GenericEventArgs < User > ( newUser ) ) ;
return newUser ;
}
public void DeleteUser ( Data. Entities . User user )
public void DeleteUser ( User user )
{
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
using var dbContext = _dbProvider . CreateContext ( ) ;
var dbContext = _dbProvider . CreateContext ( ) ;
if ( ! dbContext . Users . Contains ( user ) )
{
@ -200,19 +210,20 @@ namespace Jellyfin.Server.Implementations.User
dbContext . Users . Remove ( user ) ;
dbContext . SaveChanges ( ) ;
OnUserDeleted ? . Invoke ( this , new GenericEventArgs < User > ( user ) ) ;
}
public Task ResetPassword ( Data. Entities . User user )
public Task ResetPassword ( User user )
{
return ChangePassword ( user , string . Empty ) ;
}
public void ResetEasyPassword ( Data. Entities . User user )
public void ResetEasyPassword ( User user )
{
ChangeEasyPassword ( user , string . Empty , null ) ;
}
public async Task ChangePassword ( Data. Entities . User user , string newPassword )
public async Task ChangePassword ( User user , string newPassword )
{
if ( user = = null )
{
@ -222,24 +233,18 @@ namespace Jellyfin.Server.Implementations.User
await GetAuthenticationProvider ( user ) . ChangePassword ( user , newPassword ) . ConfigureAwait ( false ) ;
await UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
OnUserPasswordChanged ? . Invoke ( this , new GenericEventArgs < Data. Entities . User> ( user ) ) ;
OnUserPasswordChanged ? . Invoke ( this , new GenericEventArgs < User> ( user ) ) ;
}
public void ChangeEasyPassword ( Data. Entities . User user , string newPassword , string newPasswordSha1 )
public void ChangeEasyPassword ( User user , string newPassword , string newPasswordSha1 )
{
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
GetAuthenticationProvider ( user ) . ChangeEasyPassword ( user , newPassword , newPasswordSha1 ) ;
UpdateUser ( user ) ;
OnUserPasswordChanged ? . Invoke ( this , new GenericEventArgs < Data. Entities . User> ( user ) ) ;
OnUserPasswordChanged ? . Invoke ( this , new GenericEventArgs < User > ( user ) ) ;
}
public UserDto GetUserDto ( Data. Entities . User user , string remoteEndPoint = null )
public UserDto GetUserDto ( User user , string remoteEndPoint = null )
{
return new UserDto
{
@ -271,7 +276,7 @@ namespace Jellyfin.Server.Implementations.User
MaxParentalRating = user . MaxParentalAgeRating ,
EnableUserPreferenceAccess = user . EnableUserPreferenceAccess ,
RemoteClientBitrateLimit = user . RemoteClientBitrateLimit . GetValueOrDefault ( ) ,
Authenticatio Is nProviderId = user . AuthenticationProviderId ,
Authenticatio nProviderId = user . AuthenticationProviderId ,
PasswordResetProviderId = user . PasswordResetProviderId ,
InvalidLoginAttemptCount = user . InvalidLoginAttemptCount ,
LoginAttemptsBeforeLockout = user . LoginAttemptsBeforeLockout . GetValueOrDefault ( ) ,
@ -306,7 +311,7 @@ namespace Jellyfin.Server.Implementations.User
} ;
}
public PublicUserDto GetPublicUserDto ( Data. Entities . User user , string remoteEndPoint = null )
public PublicUserDto GetPublicUserDto ( User user , string remoteEndPoint = null )
{
if ( user = = null )
{
@ -328,7 +333,7 @@ namespace Jellyfin.Server.Implementations.User
} ;
}
public async Task < Data. Entities . User> AuthenticateUser (
public async Task < User> AuthenticateUser (
string username ,
string password ,
string passwordSha1 ,
@ -341,7 +346,7 @@ namespace Jellyfin.Server.Implementations.User
throw new ArgumentNullException ( nameof ( username ) ) ;
}
var user = Users . FirstOrDefault( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
var user = Users . ToList( ) . FirstOrDefault( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
bool success ;
IAuthenticationProvider authenticationProvider ;
@ -370,7 +375,7 @@ namespace Jellyfin.Server.Implementations.User
// Search the database for the user again
// the authentication provider might have created it
user = Users
. FirstOrDefault( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
. ToList( ) . FirstOrDefault( i = > string . Equals ( username , i . Username , StringComparison . OrdinalIgnoreCase ) ) ;
if ( authenticationProvider is IHasNewUserPolicy hasNewUserPolicy )
{
@ -436,10 +441,10 @@ namespace Jellyfin.Server.Implementations.User
if ( isUserSession )
{
user . LastActivityDate = user . LastLoginDate = DateTime . UtcNow ;
UpdateUser ( user ) ;
await UpdateUser Async ( user ) . ConfigureAwait ( false ) ;
}
ResetInvalidLoginAttemptCount( user ) ;
user. InvalidLoginAttemptCount = 0 ;
_logger . LogInformation ( "Authentication request for {UserName} has succeeded." , user . Username ) ;
}
else
@ -495,14 +500,11 @@ namespace Jellyfin.Server.Implementations.User
public void AddParts ( IEnumerable < IAuthenticationProvider > authenticationProviders , IEnumerable < IPasswordResetProvider > passwordResetProviders )
{
_authenticationProviders = authenticationProviders . ToArray ( ) ;
_defaultAuthenticationProvider = _authenticationProviders . OfType < DefaultAuthenticationProvider > ( ) . First ( ) ;
_invalidAuthProvider = _authenticationProviders . OfType < InvalidAuthProvider > ( ) . First ( ) ;
_passwordResetProviders = passwordResetProviders . ToArray ( ) ;
_defaultPasswordResetProvider = passwordResetProviders . OfType < DefaultPasswordResetProvider > ( ) . First ( ) ;
_invalidAuthProvider = _authenticationProviders . OfType < InvalidAuthProvider > ( ) . First ( ) ;
_defaultAuthenticationProvider = _authenticationProviders . OfType < DefaultAuthenticationProvider > ( ) . First ( ) ;
_defaultPasswordResetProvider = _passwordResetProviders . OfType < DefaultPasswordResetProvider > ( ) . First ( ) ;
}
public NameIdPair [ ] GetAuthenticationProviders ( )
@ -563,7 +565,7 @@ namespace Jellyfin.Server.Implementations.User
user . MaxParentalAgeRating = policy . MaxParentalRating ;
user . EnableUserPreferenceAccess = policy . EnableUserPreferenceAccess ;
user . RemoteClientBitrateLimit = policy . RemoteClientBitrateLimit ;
user . AuthenticationProviderId = policy . Authenticatio Is nProviderId;
user . AuthenticationProviderId = policy . Authenticatio nProviderId;
user . PasswordResetProviderId = policy . PasswordResetProviderId ;
user . InvalidLoginAttemptCount = policy . InvalidLoginAttemptCount ;
user . LoginAttemptsBeforeLockout = policy . LoginAttemptsBeforeLockout = = - 1
@ -604,28 +606,25 @@ namespace Jellyfin.Server.Implementations.User
user . SetPreference ( PreferenceKind . EnableContentDeletionFromFolders , policy . EnableContentDeletionFromFolders ) ;
}
private Data . Entities . User CreateUserObject ( string name )
private bool IsValidUsername ( string name )
{
return new Data . Entities . User (
username : name ,
mustUpdatePassword : false ,
authenticationProviderId : _defaultAuthenticationProvider . GetType ( ) . FullName ,
invalidLoginAttemptCount : - 1 ,
subtitleMode : SubtitlePlaybackMode . Default ,
playDefaultAudioTrack : true ) ;
// 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 ('), and periods (.)
return Regex . IsMatch ( name , @"^[\w\-'._@]*$" ) ;
}
private IAuthenticationProvider GetAuthenticationProvider ( Data. Entities . User user )
private IAuthenticationProvider GetAuthenticationProvider ( User user )
{
return GetAuthenticationProviders ( user ) [ 0 ] ;
}
private IPasswordResetProvider GetPasswordResetProvider ( Data. Entities . User user )
private IPasswordResetProvider GetPasswordResetProvider ( User user )
{
return GetPasswordResetProviders ( user ) [ 0 ] ;
}
private IList < IAuthenticationProvider > GetAuthenticationProviders ( Data. Entities . User user )
private IList < IAuthenticationProvider > GetAuthenticationProviders ( User user )
{
var authenticationProviderId = user ? . AuthenticationProviderId ;
@ -640,7 +639,7 @@ namespace Jellyfin.Server.Implementations.User
{
// Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
_logger . LogWarning (
"User {User N ame} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected",
"User {User n ame} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected",
user ? . Username ,
user ? . AuthenticationProviderId ) ;
providers = new List < IAuthenticationProvider >
@ -652,7 +651,7 @@ namespace Jellyfin.Server.Implementations.User
return providers ;
}
private IList < IPasswordResetProvider > GetPasswordResetProviders ( Data. Entities . User user )
private IList < IPasswordResetProvider > GetPasswordResetProviders ( User user )
{
var passwordResetProviderId = user ? . PasswordResetProviderId ;
var providers = _passwordResetProviders . Where ( i = > i . IsEnabled ) . ToArray ( ) ;
@ -675,11 +674,10 @@ namespace Jellyfin.Server.Implementations.User
return providers ;
}
private async Task < ( IAuthenticationProvider authenticationProvider , string username , bool success ) >
AuthenticateLocalUser (
private async Task < ( IAuthenticationProvider authenticationProvider , string username , bool success ) > AuthenticateLocalUser (
string username ,
string password ,
Jellyfin. Data . Entities . User user ,
User user ,
string remoteEndPoint )
{
bool success = false ;
@ -721,7 +719,7 @@ namespace Jellyfin.Server.Implementations.User
IAuthenticationProvider provider ,
string username ,
string password ,
Data. Entities . User resolvedUser )
User resolvedUser )
{
try
{
@ -745,27 +743,21 @@ namespace Jellyfin.Server.Implementations.User
}
}
private void IncrementInvalidLoginAttemptCount ( Data. Entities . User user )
private void IncrementInvalidLoginAttemptCount ( User user )
{
int invalidLogins = user . InvalidLoginAttemptCount ;
int? maxInvalidLogins = user . LoginAttemptsBeforeLockout ;
if ( maxInvalidLogins . HasValue
& & invalidLogins > = maxInvalidLogins )
if ( maxInvalidLogins . HasValue & & invalidLogins > = maxInvalidLogins )
{
user . SetPermission ( PermissionKind . IsDisabled , true ) ;
OnUserLockedOut ? . Invoke ( this , new GenericEventArgs < Data. Entities . User> ( user ) ) ;
OnUserLockedOut ? . Invoke ( this , new GenericEventArgs < User> ( user ) ) ;
_logger . LogWarning (
"Disabling user {User N ame} due to {Attempts} unsuccessful login attempts.",
"Disabling user {User n ame} due to {Attempts} unsuccessful login attempts.",
user . Username ,
invalidLogins ) ;
}
UpdateUser ( user ) ;
}
private void ResetInvalidLoginAttemptCount ( Data . Entities . User user )
{
user . InvalidLoginAttemptCount = 0 ;
}
}
}