using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.ComponentModel.DataAnnotations ;
using System.ComponentModel.DataAnnotations.Schema ;
using System.Linq ;
using System.Text.Json.Serialization ;
using Jellyfin.Data.Enums ;
using Jellyfin.Data.Interfaces ;
namespace Jellyfin.Data.Entities
{
/// <summary>
/// An entity representing a user.
/// </summary>
public class User : IHasPermissions , IHasConcurrencyToken
{
/// <summary>
/// The values being delimited here are Guids, so commas work as they do not appear in Guids.
/// </summary>
private const char Delimiter = ',' ;
/// <summary>
/// Initializes a new instance of the <see cref="User"/> class.
/// Public constructor with required data.
/// </summary>
/// <param name="username">The username for the new user.</param>
/// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
/// <param name="passwordResetProviderId">The Id of the user's password reset provider.</param>
public User ( string username , string authenticationProviderId , string passwordResetProviderId )
{
if ( string . IsNullOrEmpty ( username ) )
{
throw new ArgumentNullException ( nameof ( username ) ) ;
}
if ( string . IsNullOrEmpty ( authenticationProviderId ) )
{
throw new ArgumentNullException ( nameof ( authenticationProviderId ) ) ;
}
if ( string . IsNullOrEmpty ( passwordResetProviderId ) )
{
throw new ArgumentNullException ( nameof ( passwordResetProviderId ) ) ;
}
Username = username ;
AuthenticationProviderId = authenticationProviderId ;
PasswordResetProviderId = passwordResetProviderId ;
AccessSchedules = new HashSet < AccessSchedule > ( ) ;
DisplayPreferences = new HashSet < DisplayPreferences > ( ) ;
ItemDisplayPreferences = new HashSet < ItemDisplayPreferences > ( ) ;
// Groups = new HashSet<Group>();
Permissions = new HashSet < Permission > ( ) ;
Preferences = new HashSet < Preference > ( ) ;
// ProviderMappings = new HashSet<ProviderMapping>();
// Set default values
Id = Guid . NewGuid ( ) ;
InvalidLoginAttemptCount = 0 ;
EnableUserPreferenceAccess = true ;
MustUpdatePassword = false ;
DisplayMissingEpisodes = false ;
DisplayCollectionsView = false ;
HidePlayedInLatest = true ;
RememberAudioSelections = true ;
RememberSubtitleSelections = true ;
EnableNextEpisodeAutoPlay = true ;
EnableAutoLogin = false ;
PlayDefaultAudioTrack = true ;
SubtitleMode = SubtitlePlaybackMode . Default ;
SyncPlayAccess = SyncPlayUserAccessType . CreateAndJoinGroups ;
}
/// <summary>
/// Gets or sets the Id of the user.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
[JsonIgnore]
public Guid Id { get ; set ; }
/// <summary>
/// Gets or sets the user's name.
/// </summary>
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string Username { get ; set ; }
/// <summary>
/// Gets or sets the user's password, or <c>null</c> if none is set.
/// </summary>
/// <remarks>
/// Max length = 65535.
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string? Password { get ; set ; }
/// <summary>
/// Gets or sets the user's easy password, or <c>null</c> if none is set.
/// </summary>
/// <remarks>
/// Max length = 65535.
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string? EasyPassword { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the user must update their password.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool MustUpdatePassword { get ; set ; }
/// <summary>
/// Gets or sets the audio language preference.
/// </summary>
/// <remarks>
/// Max length = 255.
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string? AudioLanguagePreference { get ; set ; }
/// <summary>
/// Gets or sets the authentication provider id.
/// </summary>
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string AuthenticationProviderId { get ; set ; }
/// <summary>
/// Gets or sets the password reset provider id.
/// </summary>
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string PasswordResetProviderId { get ; set ; }
/// <summary>
/// Gets or sets the invalid login attempt count.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public int InvalidLoginAttemptCount { get ; set ; }
/// <summary>
/// Gets or sets the last activity date.
/// </summary>
public DateTime ? LastActivityDate { get ; set ; }
/// <summary>
/// Gets or sets the last login date.
/// </summary>
public DateTime ? LastLoginDate { get ; set ; }
/// <summary>
/// Gets or sets the number of login attempts the user can make before they are locked out.
/// </summary>
public int? LoginAttemptsBeforeLockout { get ; set ; }
/// <summary>
/// Gets or sets the maximum number of active sessions the user can have at once.
/// </summary>
public int MaxActiveSessions { get ; set ; }
/// <summary>
/// Gets or sets the subtitle mode.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public SubtitlePlaybackMode SubtitleMode { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the default audio track should be played.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool PlayDefaultAudioTrack { get ; set ; }
/// <summary>
/// Gets or sets the subtitle language preference.
/// </summary>
/// <remarks>
/// Max length = 255.
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string? SubtitleLanguagePreference { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether missing episodes should be displayed.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool DisplayMissingEpisodes { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether to display the collections view.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool DisplayCollectionsView { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the user has a local password.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool EnableLocalPassword { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the server should hide played content in "Latest".
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool HidePlayedInLatest { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether to remember audio selections on played content.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool RememberAudioSelections { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether to remember subtitle selections on played content.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool RememberSubtitleSelections { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether to enable auto-play for the next episode.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool EnableNextEpisodeAutoPlay { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the user should auto-login.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool EnableAutoLogin { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the user can change their preferences.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool EnableUserPreferenceAccess { get ; set ; }
/// <summary>
/// Gets or sets the maximum parental age rating.
/// </summary>
public int? MaxParentalAgeRating { get ; set ; }
/// <summary>
/// Gets or sets the remote client bitrate limit.
/// </summary>
public int? RemoteClientBitrateLimit { get ; set ; }
/// <summary>
/// Gets or sets the internal id.
/// This is a temporary stopgap for until the library db is migrated.
/// This corresponds to the value of the index of this user in the library db.
/// </summary>
public long InternalId { get ; set ; }
/// <summary>
/// Gets or sets the user's profile image. Can be <c>null</c>.
/// </summary>
// [ForeignKey("UserId")]
public virtual ImageInfo ? ProfileImage { get ; set ; }
/// <summary>
/// Gets the user's display preferences.
/// </summary>
public virtual ICollection < DisplayPreferences > DisplayPreferences { get ; private set ; }
/// <summary>
/// Gets or sets the level of sync play permissions this user has.
/// </summary>
public SyncPlayUserAccessType SyncPlayAccess { get ; set ; }
/// <inheritdoc />
[ConcurrencyCheck]
public uint RowVersion { get ; private set ; }
/// <summary>
/// Gets the list of access schedules this user has.
/// </summary>
public virtual ICollection < AccessSchedule > AccessSchedules { get ; private set ; }
/// <summary>
/// Gets the list of item display preferences.
/// </summary>
public virtual ICollection < ItemDisplayPreferences > ItemDisplayPreferences { get ; private set ; }
/ *
/// <summary>
/// Gets the list of groups this user is a member of.
/// </summary>
public virtual ICollection < Group > Groups { get ; private set ; }
* /
/// <summary>
/// Gets the list of permissions this user has.
/// </summary>
[ForeignKey("Permission_Permissions_Guid")]
public virtual ICollection < Permission > Permissions { get ; private set ; }
/ *
/// <summary>
/// Gets the list of provider mappings this user has.
/// </summary>
public virtual ICollection < ProviderMapping > ProviderMappings { get ; private set ; }
* /
/// <summary>
/// Gets the list of preferences this user has.
/// </summary>
[ForeignKey("Preference_Preferences_Guid")]
public virtual ICollection < Preference > Preferences { get ; private set ; }
/// <inheritdoc/>
public void OnSavingChanges ( )
{
RowVersion + + ;
}
/// <summary>
/// Checks whether the user has the specified permission.
/// </summary>
/// <param name="kind">The permission kind.</param>
/// <returns><c>True</c> if the user has the specified permission.</returns>
public bool HasPermission ( PermissionKind kind )
{
return Permissions . First ( p = > p . Kind = = kind ) . Value ;
}
/// <summary>
/// Sets the given permission kind to the provided value.
/// </summary>
/// <param name="kind">The permission kind.</param>
/// <param name="value">The value to set.</param>
public void SetPermission ( PermissionKind kind , bool value )
{
Permissions . First ( p = > p . Kind = = kind ) . Value = value ;
}
/// <summary>
/// Gets the user's preferences for the given preference kind.
/// </summary>
/// <param name="preference">The preference kind.</param>
/// <returns>A string array containing the user's preferences.</returns>
public string [ ] GetPreference ( PreferenceKind preference )
{
var val = Preferences . First ( p = > p . Kind = = preference ) . Value ;
return Equals ( val , string . Empty ) ? Array . Empty < string > ( ) : val . Split ( Delimiter ) ;
}
/// <summary>
/// Gets the user's preferences for the given preference kind.
/// </summary>
/// <param name="preference">The preference kind.</param>
/// <typeparam name="T">Type of preference.</typeparam>
/// <returns>A {T} array containing the user's preference.</returns>
public T [ ] GetPreferenceValues < T > ( PreferenceKind preference )
{
var val = Preferences . First ( p = > p . Kind = = preference ) . Value ;
if ( string . IsNullOrEmpty ( val ) )
{
return Array . Empty < T > ( ) ;
}
// Convert array of {string} to array of {T}
var converter = TypeDescriptor . GetConverter ( typeof ( T ) ) ;
var stringValues = val . Split ( Delimiter ) ;
var convertedCount = 0 ;
var parsedValues = new T [ stringValues . Length ] ;
for ( var i = 0 ; i < stringValues . Length ; i + + )
{
try
{
var parsedValue = converter . ConvertFromString ( stringValues [ i ] . Trim ( ) ) ;
if ( parsedValue ! = null )
{
parsedValues [ convertedCount + + ] = ( T ) parsedValue ;
}
}
catch ( FormatException )
{
// Unable to convert value
}
}
return parsedValues [ . . convertedCount ] ;
}
/// <summary>
/// Sets the specified preference to the given value.
/// </summary>
/// <param name="preference">The preference kind.</param>
/// <param name="values">The values.</param>
public void SetPreference ( PreferenceKind preference , string [ ] values )
{
Preferences . First ( p = > p . Kind = = preference ) . Value
= string . Join ( Delimiter , values ) ;
}
/// <summary>
/// Sets the specified preference to the given value.
/// </summary>
/// <param name="preference">The preference kind.</param>
/// <param name="values">The values.</param>
/// <typeparam name="T">The type of value.</typeparam>
public void SetPreference < T > ( PreferenceKind preference , T [ ] values )
{
Preferences . First ( p = > p . Kind = = preference ) . Value
= string . Join ( Delimiter , values ) ;
}
/// <summary>
/// Checks whether this user is currently allowed to use the server.
/// </summary>
/// <returns><c>True</c> if the current time is within an access schedule, or there are no access schedules.</returns>
public bool IsParentalScheduleAllowed ( )
{
return AccessSchedules . Count = = 0
| | AccessSchedules . Any ( i = > IsParentalScheduleAllowed ( i , DateTime . UtcNow ) ) ;
}
/// <summary>
/// Checks whether the provided folder is in this user's grouped folders.
/// </summary>
/// <param name="id">The Guid of the folder.</param>
/// <returns><c>True</c> if the folder is in the user's grouped folders.</returns>
public bool IsFolderGrouped ( Guid id )
{
return Array . IndexOf ( GetPreferenceValues < Guid > ( PreferenceKind . GroupedFolders ) , id ) ! = - 1 ;
}
/// <summary>
/// Initializes the default permissions for a user. Should only be called on user creation.
/// </summary>
// TODO: make these user configurable?
public void AddDefaultPermissions ( )
{
Permissions . Add ( new Permission ( PermissionKind . IsAdministrator , false ) ) ;
Permissions . Add ( new Permission ( PermissionKind . IsDisabled , false ) ) ;
Permissions . Add ( new Permission ( PermissionKind . IsHidden , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableAllChannels , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableAllDevices , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableAllFolders , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableContentDeletion , false ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableContentDownloading , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableMediaConversion , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableMediaPlayback , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnablePlaybackRemuxing , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnablePublicSharing , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableRemoteAccess , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableSyncTranscoding , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableAudioPlaybackTranscoding , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableLiveTvAccess , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableLiveTvManagement , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableSharedDeviceControl , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableVideoPlaybackTranscoding , true ) ) ;
Permissions . Add ( new Permission ( PermissionKind . ForceRemoteSourceTranscoding , false ) ) ;
Permissions . Add ( new Permission ( PermissionKind . EnableRemoteControlOfOtherUsers , false ) ) ;
}
/// <summary>
/// Initializes the default preferences. Should only be called on user creation.
/// </summary>
public void AddDefaultPreferences ( )
{
foreach ( var val in Enum . GetValues ( typeof ( PreferenceKind ) ) . Cast < PreferenceKind > ( ) )
{
Preferences . Add ( new Preference ( val , string . Empty ) ) ;
}
}
private static bool IsParentalScheduleAllowed ( AccessSchedule schedule , DateTime date )
{
var localTime = date . ToLocalTime ( ) ;
var hour = localTime . TimeOfDay . TotalHours ;
return DayOfWeekHelper . GetDaysOfWeek ( schedule . DayOfWeek ) . Contains ( localTime . DayOfWeek )
& & hour > = schedule . StartHour
& & hour < = schedule . EndHour ;
}
}
}