aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs225
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs35
-rw-r--r--Jellyfin.Data/Entities/AccessSchedule.cs18
-rw-r--r--Jellyfin.Data/Entities/Group.cs41
-rw-r--r--Jellyfin.Data/Entities/Permission.cs53
-rw-r--r--Jellyfin.Data/Entities/Preference.cs81
-rw-r--r--Jellyfin.Data/Entities/User.cs141
-rw-r--r--Jellyfin.Data/IHasPermissions.cs10
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs10
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDbProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs)192
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs)198
-rw-r--r--Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs186
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs (renamed from Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs)2
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs (renamed from Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs)5
-rw-r--r--Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs (renamed from Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs)15
-rw-r--r--Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs (renamed from Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs)2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs (renamed from Jellyfin.Server.Implementations/User/UserManager.cs)154
-rw-r--r--Jellyfin.Server/CoreAppHost.cs6
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj1
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs100
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs8
23 files changed, 997 insertions, 489 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index b2fe6a3ab..56b103763 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -562,7 +562,6 @@ namespace Emby.Server.Implementations
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
- serviceCollection.AddSingleton<IUserManager, UserManager>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
new file mode 100644
index 000000000..63d0321b7
--- /dev/null
+++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
@@ -0,0 +1,225 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Emby.Server.Implementations.Data
+{
+ /// <summary>
+ /// Class SQLiteDisplayPreferencesRepository.
+ /// </summary>
+ public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
+ {
+ private readonly IFileSystem _fileSystem;
+
+ private readonly JsonSerializerOptions _jsonOptions;
+
+ public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem)
+ : base(logger)
+ {
+ _fileSystem = fileSystem;
+
+ _jsonOptions = JsonDefaults.GetOptions();
+
+ DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
+ }
+
+ /// <summary>
+ /// Gets the name of the repository.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => "SQLite";
+
+ public void Initialize()
+ {
+ try
+ {
+ InitializeInternal();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error loading database file. Will reset and retry.");
+
+ _fileSystem.DeleteFile(DbFilePath);
+
+ InitializeInternal();
+ }
+ }
+
+ /// <summary>
+ /// Opens the connection to the database
+ /// </summary>
+ /// <returns>Task.</returns>
+ private void InitializeInternal()
+ {
+ string[] queries =
+ {
+ "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
+ "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
+ };
+
+ using (var connection = GetConnection())
+ {
+ connection.RunQueries(queries);
+ }
+ }
+
+ /// <summary>
+ /// Save the display preferences associated with an item in the repo
+ /// </summary>
+ /// <param name="displayPreferences">The display preferences.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="client">The client.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <exception cref="ArgumentNullException">item</exception>
+ public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
+ {
+ if (displayPreferences == null)
+ {
+ throw new ArgumentNullException(nameof(displayPreferences));
+ }
+
+ if (string.IsNullOrEmpty(displayPreferences.Id))
+ {
+ throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using (var connection = GetConnection())
+ {
+ connection.RunInTransaction(
+ db => SaveDisplayPreferences(displayPreferences, userId, client, db),
+ TransactionMode);
+ }
+ }
+
+ private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection)
+ {
+ var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions);
+
+ using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
+ {
+ statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
+ statement.TryBind("@userId", userId.ToByteArray());
+ statement.TryBind("@client", client);
+ statement.TryBind("@data", serialized);
+
+ statement.MoveNext();
+ }
+ }
+
+ /// <summary>
+ /// Save all display preferences associated with a user in the repo
+ /// </summary>
+ /// <param name="displayPreferences">The display preferences.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <exception cref="ArgumentNullException">item</exception>
+ public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
+ {
+ if (displayPreferences == null)
+ {
+ throw new ArgumentNullException(nameof(displayPreferences));
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using (var connection = GetConnection())
+ {
+ connection.RunInTransaction(
+ db =>
+ {
+ foreach (var displayPreference in displayPreferences)
+ {
+ SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
+ }
+ },
+ TransactionMode);
+ }
+ }
+
+ /// <summary>
+ /// Gets the display preferences.
+ /// </summary>
+ /// <param name="displayPreferencesId">The display preferences id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="client">The client.</param>
+ /// <returns>Task{DisplayPreferences}.</returns>
+ /// <exception cref="ArgumentNullException">item</exception>
+ public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client)
+ {
+ if (string.IsNullOrEmpty(displayPreferencesId))
+ {
+ throw new ArgumentNullException(nameof(displayPreferencesId));
+ }
+
+ var guidId = displayPreferencesId.GetMD5();
+
+ using (var connection = GetConnection(true))
+ {
+ using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
+ {
+ statement.TryBind("@id", guidId.ToByteArray());
+ statement.TryBind("@userId", userId.ToByteArray());
+ statement.TryBind("@client", client);
+
+ foreach (var row in statement.ExecuteQuery())
+ {
+ return Get(row);
+ }
+ }
+ }
+
+ return new DisplayPreferences
+ {
+ Id = guidId.ToString("N", CultureInfo.InvariantCulture)
+ };
+ }
+
+ /// <summary>
+ /// Gets all display preferences for the given user.
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{DisplayPreferences}.</returns>
+ /// <exception cref="ArgumentNullException">item</exception>
+ public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
+ {
+ var list = new List<DisplayPreferences>();
+
+ using (var connection = GetConnection(true))
+ using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
+ {
+ statement.TryBind("@userId", userId.ToByteArray());
+
+ foreach (var row in statement.ExecuteQuery())
+ {
+ list.Add(Get(row));
+ }
+ }
+
+ return list;
+ }
+
+ private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
+ => JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions);
+
+ public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
+ => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
+
+ public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
+ => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
+ }
+}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 84b16f1c7..54c26a8a5 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
@@ -14,7 +15,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
@@ -27,6 +27,7 @@ using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
namespace Emby.Server.Implementations.Session
{
@@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Session
string deviceId,
string deviceName,
string remoteEndPoint,
- Jellyfin.Data.Entities.User user)
+ User user)
{
CheckDisposed();
@@ -438,7 +439,7 @@ namespace Emby.Server.Implementations.Session
string deviceId,
string deviceName,
string remoteEndPoint,
- Jellyfin.Data.Entities.User user)
+ User user)
{
CheckDisposed();
@@ -457,7 +458,7 @@ namespace Emby.Server.Implementations.Session
sessionInfo.UserId = user?.Id ?? Guid.Empty;
sessionInfo.UserName = user?.Username;
- sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
+ sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user);
sessionInfo.RemoteEndPoint = remoteEndPoint;
sessionInfo.Client = appName;
@@ -483,7 +484,7 @@ namespace Emby.Server.Implementations.Session
string deviceId,
string deviceName,
string remoteEndPoint,
- Jellyfin.Data.Entities.User user)
+ User user)
{
var sessionInfo = new SessionInfo(this, _logger)
{
@@ -497,7 +498,7 @@ namespace Emby.Server.Implementations.Session
sessionInfo.UserId = user?.Id ?? Guid.Empty;
sessionInfo.UserName = username;
- sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
+ sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user);
sessionInfo.RemoteEndPoint = remoteEndPoint;
if (string.IsNullOrEmpty(deviceName))
@@ -520,9 +521,9 @@ namespace Emby.Server.Implementations.Session
return sessionInfo;
}
- private List<Jellyfin.Data.Entities.User> GetUsers(SessionInfo session)
+ private List<User> GetUsers(SessionInfo session)
{
- var users = new List<Jellyfin.Data.Entities.User>();
+ var users = new List<User>();
if (session.UserId != Guid.Empty)
{
@@ -680,7 +681,7 @@ namespace Emby.Server.Implementations.Session
/// </summary>
/// <param name="user">The user object.</param>
/// <param name="item">The item.</param>
- private void OnPlaybackStart(Jellyfin.Data.Entities.User user, BaseItem item)
+ private void OnPlaybackStart(User user, BaseItem item)
{
var data = _userDataManager.GetUserData(user, item);
@@ -763,7 +764,7 @@ namespace Emby.Server.Implementations.Session
StartIdleCheckTimer();
}
- private void OnPlaybackProgress(Jellyfin.Data.Entities.User user, BaseItem item, PlaybackProgressInfo info)
+ private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
{
var data = _userDataManager.GetUserData(user, item);
@@ -789,7 +790,7 @@ namespace Emby.Server.Implementations.Session
}
}
- private static bool UpdatePlaybackSettings(Jellyfin.Data.Entities.User user, PlaybackProgressInfo info, UserItemData data)
+ private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
{
var changed = false;
@@ -949,7 +950,7 @@ namespace Emby.Server.Implementations.Session
_logger);
}
- private bool OnPlaybackStopped(Jellyfin.Data.Entities.User user, BaseItem item, long? positionTicks, bool playbackFailed)
+ private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed)
{
bool playedToCompletion = false;
@@ -1163,7 +1164,7 @@ namespace Emby.Server.Implementations.Session
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
}
- private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, Jellyfin.Data.Entities.User user)
+ private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -1216,7 +1217,7 @@ namespace Emby.Server.Implementations.Session
return new[] { item };
}
- private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, Jellyfin.Data.Entities.User user)
+ private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -1399,7 +1400,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- Jellyfin.Data.Entities.User user = null;
+ User user = null;
if (request.UserId != Guid.Empty)
{
user = _userManager.GetUserById(request.UserId);
@@ -1455,7 +1456,7 @@ namespace Emby.Server.Implementations.Session
return returnResult;
}
- private string GetAuthorizationToken(Jellyfin.Data.Entities.User user, string deviceId, string app, string appVersion, string deviceName)
+ private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(
new AuthenticationInfoQuery
@@ -1701,7 +1702,7 @@ namespace Emby.Server.Implementations.Session
return info;
}
- private string GetImageCacheTag(Jellyfin.Data.Entities.User user)
+ private string GetImageCacheTag(User user)
{
try
{
diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs
index 7966cdb50..711e94dd1 100644
--- a/Jellyfin.Data/Entities/AccessSchedule.cs
+++ b/Jellyfin.Data/Entities/AccessSchedule.cs
@@ -1,5 +1,7 @@
-using System.ComponentModel.DataAnnotations;
+using System;
+using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
@@ -20,8 +22,9 @@ namespace Jellyfin.Data.Entities
/// <param name="dayOfWeek">The day of the week.</param>
/// <param name="startHour">The start hour.</param>
/// <param name="endHour">The end hour.</param>
- public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour)
+ public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
{
+ UserId = userId;
DayOfWeek = dayOfWeek;
StartHour = startHour;
EndHour = endHour;
@@ -34,15 +37,20 @@ namespace Jellyfin.Data.Entities
/// <param name="startHour">The start hour.</param>
/// <param name="endHour">The end hour.</param>
/// <returns>The newly created instance.</returns>
- public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour)
+ public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
{
- return new AccessSchedule(dayOfWeek, startHour, endHour);
+ return new AccessSchedule(dayOfWeek, startHour, endHour, userId);
}
+ [JsonIgnore]
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; protected set; }
+ public int Id { get; set; }
+
+ [Required]
+ [ForeignKey("Id")]
+ public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the day of week.
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
index 54f9f4905..017fb2234 100644
--- a/Jellyfin.Data/Entities/Group.cs
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities
{
- public partial class Group
+ public partial class Group : IHasPermissions, ISavingChanges
{
partial void Init();
@@ -14,7 +14,7 @@ namespace Jellyfin.Data.Entities
/// </summary>
protected Group()
{
- GroupPermissions = new HashSet<Permission>();
+ Permissions = new HashSet<Permission>();
ProviderMappings = new HashSet<ProviderMapping>();
Preferences = new HashSet<Preference>();
@@ -22,27 +22,21 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static Group CreateGroupUnsafe()
- {
- return new Group();
- }
-
- /// <summary>
/// Public constructor with required data
/// </summary>
/// <param name="name"></param>
- /// <param name="_user0"></param>
- public Group(string name, User _user0)
+ /// <param name="user"></param>
+ public Group(string name, User user)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
- this.Name = name;
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
- if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
- _user0.Groups.Add(this);
+ this.Name = name;
+ user.Groups.Add(this);
- this.GroupPermissions = new HashSet<Permission>();
+ this.Permissions = new HashSet<Permission>();
this.ProviderMappings = new HashSet<ProviderMapping>();
this.Preferences = new HashSet<Preference>();
@@ -54,9 +48,9 @@ namespace Jellyfin.Data.Entities
/// </summary>
/// <param name="name"></param>
/// <param name="_user0"></param>
- public static Group Create(string name, User _user0)
+ public static Group Create(string name, User user)
{
- return new Group(name, _user0);
+ return new Group(name, user);
}
/*************************************************************************
@@ -68,8 +62,7 @@ namespace Jellyfin.Data.Entities
/// </summary>
[Key]
[Required]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; protected set; }
+ public Guid Id { get; protected set; }
/// <summary>
/// Required, Max length = 255
@@ -96,13 +89,13 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("Permission_GroupPermissions_Id")]
- public virtual ICollection<Permission> GroupPermissions { get; protected set; }
+ public ICollection<Permission> Permissions { get; protected set; }
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
- public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
+ public ICollection<ProviderMapping> ProviderMappings { get; protected set; }
[ForeignKey("Preference_Preferences_Id")]
- public virtual ICollection<Preference> Preferences { get; protected set; }
+ public ICollection<Preference> Preferences { get; protected set; }
}
}
diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs
index 0b5b52cbd..136e7abd3 100644
--- a/Jellyfin.Data/Entities/Permission.cs
+++ b/Jellyfin.Data/Entities/Permission.cs
@@ -3,10 +3,11 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.CompilerServices;
+using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
- public partial class Permission
+ public partial class Permission : ISavingChanges
{
partial void Init();
@@ -19,32 +20,15 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static Permission CreatePermissionUnsafe()
- {
- return new Permission();
- }
-
- /// <summary>
/// Public constructor with required data
/// </summary>
/// <param name="kind"></param>
/// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public Permission(Enums.PermissionKind kind, bool value, User _user0, Group _group1)
+ /// <param name="holderId"></param>
+ public Permission(PermissionKind kind, bool value)
{
- this.Kind = kind;
-
- this.Value = value;
-
- if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
- _user0.Permissions.Add(this);
-
- if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
- _group1.GroupPermissions.Add(this);
-
+ Kind = kind;
+ Value = value;
Init();
}
@@ -54,11 +38,10 @@ namespace Jellyfin.Data.Entities
/// </summary>
/// <param name="kind"></param>
/// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1)
+ /// <param name="holderId"></param>
+ public static Permission Create(PermissionKind kind, bool value)
{
- return new Permission(kind, value, _user0, _group1);
+ return new Permission(kind, value);
}
/*************************************************************************
@@ -76,31 +59,32 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Backing field for Kind
/// </summary>
- protected Enums.PermissionKind _Kind;
+ protected PermissionKind _Kind;
/// <summary>
/// When provided in a partial class, allows value of Kind to be changed before setting.
/// </summary>
- partial void SetKind(Enums.PermissionKind oldValue, ref Enums.PermissionKind newValue);
+ partial void SetKind(PermissionKind oldValue, ref PermissionKind newValue);
/// <summary>
/// When provided in a partial class, allows value of Kind to be changed before returning.
/// </summary>
- partial void GetKind(ref Enums.PermissionKind result);
+ partial void GetKind(ref PermissionKind result);
/// <summary>
/// Required
/// </summary>
[Required]
- public Enums.PermissionKind Kind
+ public PermissionKind Kind
{
get
{
- Enums.PermissionKind value = _Kind;
+ PermissionKind value = _Kind;
GetKind(ref value);
- return (_Kind = value);
+ return _Kind = value;
}
+
set
{
- Enums.PermissionKind oldValue = _Kind;
+ PermissionKind oldValue = _Kind;
SetKind(oldValue, ref value);
if (oldValue != value)
{
@@ -117,7 +101,7 @@ namespace Jellyfin.Data.Entities
public bool Value { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrencyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -138,7 +122,6 @@ namespace Jellyfin.Data.Entities
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
-
}
}
diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs
index 505f52e6b..56a07d440 100644
--- a/Jellyfin.Data/Entities/Preference.cs
+++ b/Jellyfin.Data/Entities/Preference.cs
@@ -1,63 +1,33 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
- public partial class Preference
+ /// <summary>
+ /// An entity representing a preference attached to a user or group.
+ /// </summary>
+ public class Preference : ISavingChanges
{
- partial void Init();
-
- /// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected Preference()
- {
- Init();
- }
-
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// Initializes a new instance of the <see cref="Preference"/> class.
+ /// Public constructor with required data.
/// </summary>
- public static Preference CreatePreferenceUnsafe()
+ /// <param name="kind">The preference kind.</param>
+ /// <param name="value">The value.</param>
+ public Preference(PreferenceKind kind, string value)
{
- return new Preference();
+ Kind = kind;
+ Value = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
- /// Public constructor with required data
- /// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public Preference(Enums.PreferenceKind kind, string value, User _user0, Group _group1)
- {
- this.Kind = kind;
-
- if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));
- this.Value = value;
-
- if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
- _user0.Preferences.Add(this);
-
- if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
- _group1.Preferences.Add(this);
-
-
- Init();
- }
-
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
+ /// Initializes a new instance of the <see cref="Preference"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1)
+ protected Preference()
{
- return new Preference(kind, value, _user0, _group1);
}
/*************************************************************************
@@ -76,7 +46,7 @@ namespace Jellyfin.Data.Entities
/// Required
/// </summary>
[Required]
- public Enums.PreferenceKind Kind { get; set; }
+ public PreferenceKind Kind { get; set; }
/// <summary>
/// Required, Max length = 65535
@@ -87,21 +57,28 @@ namespace Jellyfin.Data.Entities
public string Value { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrencyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="kind">The preference kind.</param>
+ /// <param name="value">The value.</param>
+ /// <returns>The new instance.</returns>
+ public static Preference Create(PreferenceKind kind, string value)
+ {
+ return new Preference(kind, value);
+ }
+
+ /// <inheritdoc/>
public void OnSavingChanges()
{
RowVersion++;
}
-
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 17913959e..7252ef230 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -9,45 +9,23 @@ using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
- public partial class User
+ /// <summary>
+ /// An entity representing a user.
+ /// </summary>
+ public partial class User : IHasPermissions, ISavingChanges
{
/// <summary>
/// The values being delimited here are Guids, so commas work as they do not appear in Guids.
/// </summary>
private const char Delimiter = ',';
- partial void Init();
-
- /// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected User()
- {
- Groups = new HashSet<Group>();
- Permissions = new HashSet<Permission>();
- ProviderMappings = new HashSet<ProviderMapping>();
- Preferences = new HashSet<Preference>();
- AccessSchedules = new HashSet<AccessSchedule>();
-
- Init();
- }
-
/// <summary>
- /// Public constructor with required data
+ /// Initializes a new instance of the <see cref="User"/> class.
+ /// Public constructor with required data.
/// </summary>
- /// <param name="username"></param>
- /// <param name="mustUpdatePassword"></param>
- /// <param name="authenticationProviderId"></param>
- /// <param name="invalidLoginAttemptCount"></param>
- /// <param name="subtitleMode"></param>
- /// <param name="playDefaultAudioTrack"></param>
- public User(
- string username,
- bool mustUpdatePassword,
- string authenticationProviderId,
- int invalidLoginAttemptCount,
- SubtitlePlaybackMode subtitleMode,
- bool playDefaultAudioTrack)
+ /// <param name="username">The username for the new user.</param>
+ /// <param name="authenticationProviderId">The authentication provider's Id</param>
+ public User(string username, string authenticationProviderId)
{
if (string.IsNullOrEmpty(username))
{
@@ -60,11 +38,7 @@ namespace Jellyfin.Data.Entities
}
Username = username;
- MustUpdatePassword = mustUpdatePassword;
AuthenticationProviderId = authenticationProviderId;
- InvalidLoginAttemptCount = invalidLoginAttemptCount;
- SubtitleMode = subtitleMode;
- PlayDefaultAudioTrack = playDefaultAudioTrack;
Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>();
@@ -74,6 +48,8 @@ namespace Jellyfin.Data.Entities
// Set default values
Id = Guid.NewGuid();
+ InvalidLoginAttemptCount = 0;
+ MustUpdatePassword = false;
DisplayMissingEpisodes = false;
DisplayCollectionsView = false;
HidePlayedInLatest = true;
@@ -81,36 +57,40 @@ namespace Jellyfin.Data.Entities
RememberSubtitleSelections = true;
EnableNextEpisodeAutoPlay = true;
EnableAutoLogin = false;
+ PlayDefaultAudioTrack = true;
+ SubtitleMode = SubtitlePlaybackMode.Default;
+ AddDefaultPermissions();
+ AddDefaultPreferences();
Init();
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// Initializes a new instance of the <see cref="User"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- public static User CreateUserUnsafe()
+ protected User()
{
- return new User();
+ Groups = new HashSet<Group>();
+ Permissions = new HashSet<Permission>();
+ ProviderMappings = new HashSet<ProviderMapping>();
+ Preferences = new HashSet<Preference>();
+ AccessSchedules = new HashSet<AccessSchedule>();
+
+ AddDefaultPermissions();
+ AddDefaultPreferences();
+ Init();
}
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="username"></param>
- /// <param name="mustUpdatePassword"></param>
- /// <param name="authenticationProviderId"></param>
- /// <param name="invalidLoginAttemptCount"></param>
- /// <param name="subtitleMode"></param>
- /// <param name="playDefaultAudioTrack"></param>
- public static User Create(
- string username,
- bool mustUpdatePassword,
- string authenticationProviderId,
- int invalidLoginAttemptCount,
- SubtitlePlaybackMode subtitleMode,
- bool playDefaultAudioTrack)
+ /// <param name="username">The username for the created user.</param>
+ /// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
+ /// <returns>The created instance.</returns>
+ public static User Create(string username, string authenticationProviderId)
{
- return new User(username, mustUpdatePassword, authenticationProviderId, invalidLoginAttemptCount, subtitleMode, playDefaultAudioTrack);
+ return new User(username, authenticationProviderId);
}
/*************************************************************************
@@ -131,7 +111,6 @@ namespace Jellyfin.Data.Entities
[Required]
[MaxLength(255)]
[StringLength(255)]
- [JsonPropertyName("Name")]
public string Username { get; set; }
/// <summary>
@@ -199,6 +178,7 @@ namespace Jellyfin.Data.Entities
public bool PlayDefaultAudioTrack { get; set; }
/// <summary>
+ /// Gets or sets the subtitle language preference.
/// Max length = 255
/// </summary>
[MaxLength(255)]
@@ -237,6 +217,7 @@ namespace Jellyfin.Data.Entities
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>
@@ -246,7 +227,8 @@ namespace Jellyfin.Data.Entities
public ImageInfo ProfileImage { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -260,23 +242,25 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
- [ForeignKey("Group_Groups_Id")]
+ [ForeignKey("Group_Groups_Guid")]
public ICollection<Group> Groups { get; protected set; }
- [ForeignKey("Permission_Permissions_Id")]
+ [ForeignKey("Permission_Permissions_Guid")]
public ICollection<Permission> Permissions { get; protected set; }
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
public ICollection<ProviderMapping> ProviderMappings { get; protected set; }
- [ForeignKey("Preference_Preferences_Id")]
+ [ForeignKey("Preference_Preferences_Guid")]
public ICollection<Preference> Preferences { get; protected set; }
public ICollection<AccessSchedule> AccessSchedules { get; protected set; }
+ partial void Init();
+
public bool HasPermission(PermissionKind permission)
{
- return Permissions.Select(p => p.Kind).Contains(permission);
+ return Permissions.First(p => p.Kind == permission).Value;
}
public void SetPermission(PermissionKind kind, bool value)
@@ -287,11 +271,12 @@ namespace Jellyfin.Data.Entities
public string[] GetPreference(PreferenceKind preference)
{
- return Preferences
+ var val = Preferences
.Where(p => p.Kind == preference)
.Select(p => p.Value)
- .First()
- .Split(Delimiter);
+ .First();
+
+ return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter);
}
public void SetPreference(PreferenceKind preference, string[] values)
@@ -332,5 +317,39 @@ namespace Jellyfin.Data.Entities
return hour >= schedule.StartHour && hour <= schedule.EndHour;
}
+
+ // TODO: make these user configurable?
+ private void AddDefaultPermissions()
+ {
+ Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
+ Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
+ Permissions.Add(new Permission(PermissionKind.IsHidden, false));
+ Permissions.Add(new Permission(PermissionKind.EnableAllChannels, false));
+ Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true));
+ Permissions.Add(new Permission(PermissionKind.EnableAllFolders, false));
+ 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));
+ }
+
+ private void AddDefaultPreferences()
+ {
+ foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>())
+ {
+ Preferences.Add(new Preference(val, string.Empty));
+ }
+ }
}
}
diff --git a/Jellyfin.Data/IHasPermissions.cs b/Jellyfin.Data/IHasPermissions.cs
new file mode 100644
index 000000000..a77e51e1e
--- /dev/null
+++ b/Jellyfin.Data/IHasPermissions.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using Jellyfin.Data.Entities;
+
+namespace Jellyfin.Data
+{
+ public interface IHasPermissions
+ {
+ ICollection<Permission> Permissions { get; }
+ }
+}
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index 9ac97a131..8eb35ec87 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -16,6 +16,13 @@ namespace Jellyfin.Server.Implementations
public partial class JellyfinDb : DbContext
{
public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
+
+ public virtual DbSet<Group> Groups { get; set; }
+
+ public virtual DbSet<Permission> Permissions { get; set; }
+
+ public virtual DbSet<Preference> Preferences { get; set; }
+
public virtual DbSet<Data.Entities.User> Users { get; set; }
/*public virtual DbSet<Artwork> Artwork { get; set; }
public virtual DbSet<Book> Books { get; set; }
@@ -30,7 +37,6 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<Episode> Episodes { get; set; }
public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
public virtual DbSet<Genre> Genres { get; set; }
- public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<Library> Libraries { get; set; }
public virtual DbSet<LibraryItem> LibraryItems { get; set; }
public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
@@ -43,12 +49,10 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
- public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<PersonRole> PersonRoles { get; set; }
public virtual DbSet<Photo> Photo { get; set; }
public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
- public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
public virtual DbSet<Rating> Ratings { get; set; }
diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
index eab531d38..8f5c19900 100644
--- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
@@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations
public JellyfinDbProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
- serviceProvider.GetService<JellyfinDb>().Database.Migrate();
+ serviceProvider.GetRequiredService<JellyfinDb>().Database.Migrate();
}
/// <summary>
diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs
index 8313c6a3b..36c58c8ca 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
#pragma warning disable SA1601
// <auto-generated />
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDb))]
- [Migration("20200504195702_UserSchema")]
- partial class UserSchema
+ [Migration("20200517002411_AddUsers")]
+ partial class AddUsers
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
@@ -22,6 +22,31 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.3");
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property<double>("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property<double>("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedule");
+ });
+
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
@@ -65,17 +90,17 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.ToTable("ActivityLog");
+ b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
{
- b.Property<int>("Id")
+ b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
+ .HasColumnType("TEXT");
- b.Property<int?>("Group_Groups_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Group_Groups_Guid")
+ .HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
@@ -88,9 +113,27 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.HasIndex("Group_Groups_Id");
+ b.HasIndex("Group_Groups_Guid");
+
+ b.ToTable("Groups");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
- b.ToTable("Group");
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ImageInfo");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@@ -102,11 +145,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<int>("Kind")
.HasColumnType("INTEGER");
- b.Property<int?>("Permission_GroupPermissions_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Permission_GroupPermissions_Id")
+ .HasColumnType("TEXT");
- b.Property<int?>("Permission_Permissions_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Permission_Permissions_Guid")
+ .HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
@@ -119,9 +162,9 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("Permission_GroupPermissions_Id");
- b.HasIndex("Permission_Permissions_Id");
+ b.HasIndex("Permission_Permissions_Guid");
- b.ToTable("Permission");
+ b.ToTable("Permissions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
@@ -133,8 +176,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<int>("Kind")
.HasColumnType("INTEGER");
- b.Property<int?>("Preference_Preferences_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Preference_Preferences_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<Guid?>("Preference_Preferences_Id")
+ .HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
@@ -147,9 +193,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
+ b.HasIndex("Preference_Preferences_Guid");
+
b.HasIndex("Preference_Preferences_Id");
- b.ToTable("Preference");
+ b.ToTable("Preferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
@@ -163,8 +211,8 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT")
.HasMaxLength(65535);
- b.Property<int?>("ProviderMapping_ProviderMappings_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("ProviderMapping_ProviderMappings_Id")
+ .HasColumnType("TEXT");
b.Property<string>("ProviderName")
.IsRequired()
@@ -189,12 +237,11 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
- b.Property<int>("Id")
+ b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
+ .HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
- .IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
@@ -203,71 +250,86 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT")
.HasMaxLength(255);
- b.Property<bool?>("DisplayCollectionsView")
+ b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
- b.Property<bool?>("DisplayMissingEpisodes")
+ b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
- b.Property<bool?>("EnableNextEpisodeAutoPlay")
+ b.Property<string>("EasyPassword")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
- b.Property<bool?>("EnableUserPreferenceAccess")
+ b.Property<bool>("EnableLocalPassword")
.HasColumnType("INTEGER");
- b.Property<string>("GroupedFolders")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<bool>("EnableNextEpisodeAutoPlay")
+ .HasColumnType("INTEGER");
- b.Property<bool?>("HidePlayedInLatest")
+ b.Property<bool>("EnableUserPreferenceAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("HidePlayedInLatest")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("InternalId")
.HasColumnType("INTEGER");
b.Property<int>("InvalidLoginAttemptCount")
.HasColumnType("INTEGER");
- b.Property<string>("LatestItemExcludes")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<DateTime>("LastActivityDate")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastLoginDate")
+ .HasColumnType("TEXT");
b.Property<int?>("LoginAttemptsBeforeLockout")
.HasColumnType("INTEGER");
- b.Property<bool>("MustUpdatePassword")
+ b.Property<int?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
- b.Property<string>("MyMediaExcludes")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<bool>("MustUpdatePassword")
+ .HasColumnType("INTEGER");
- b.Property<string>("OrderedViews")
+ b.Property<string>("Password")
.HasColumnType("TEXT")
.HasMaxLength(65535);
- b.Property<string>("Password")
+ b.Property<string>("PasswordResetProviderId")
+ .IsRequired()
.HasColumnType("TEXT")
- .HasMaxLength(65535);
+ .HasMaxLength(255);
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
- b.Property<bool?>("RememberAudioSelections")
+ b.Property<int?>("ProfileImageId")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberAudioSelections")
.HasColumnType("INTEGER");
- b.Property<bool?>("RememberSubtitleSelections")
+ b.Property<bool>("RememberSubtitleSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("RemoteClientBitrateLimit")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
- b.Property<string>("SubtitleLanguagePrefernce")
+ b.Property<string>("SubtitleLanguagePreference")
.HasColumnType("TEXT")
.HasMaxLength(255);
- b.Property<string>("SubtitleMode")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasMaxLength(255);
+ b.Property<int>("SubtitleMode")
+ .HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
@@ -276,34 +338,45 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.ToTable("User");
+ b.HasIndex("ProfileImageId");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("AccessSchedules")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Groups")
- .HasForeignKey("Group_Groups_Id");
+ .HasForeignKey("Group_Groups_Guid");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.HasOne("Jellyfin.Data.Entities.Group", null)
- .WithMany("GroupPermissions")
+ .WithMany("Permissions")
.HasForeignKey("Permission_GroupPermissions_Id");
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Permissions")
- .HasForeignKey("Permission_Permissions_Id");
+ .HasForeignKey("Permission_Permissions_Guid");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
- b.HasOne("Jellyfin.Data.Entities.Group", null)
+ b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Preferences")
- .HasForeignKey("Preference_Preferences_Id");
+ .HasForeignKey("Preference_Preferences_Guid");
- b.HasOne("Jellyfin.Data.Entities.User", null)
+ b.HasOne("Jellyfin.Data.Entities.Group", null)
.WithMany("Preferences")
.HasForeignKey("Preference_Preferences_Id");
});
@@ -318,6 +391,13 @@ namespace Jellyfin.Server.Implementations.Migrations
.WithMany("ProviderMappings")
.HasForeignKey("ProviderMapping_ProviderMappings_Id");
});
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage")
+ .WithMany()
+ .HasForeignKey("ProfileImageId");
+ });
#pragma warning restore 612, 618
}
}
diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs
index f24ccccbf..55c6f371c 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs
@@ -1,74 +1,125 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
#pragma warning disable SA1601
+using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Jellyfin.Server.Implementations.Migrations
{
- public partial class UserSchema : Migration
+ public partial class AddUsers : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
- name: "User",
+ name: "ImageInfo",
schema: "jellyfin",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
+ Path = table.Column<string>(nullable: false),
+ LastModified = table.Column<DateTime>(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ImageInfo", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ schema: "jellyfin",
+ columns: table => new
+ {
+ Id = table.Column<Guid>(nullable: false),
Username = table.Column<string>(maxLength: 255, nullable: false),
Password = table.Column<string>(maxLength: 65535, nullable: true),
+ EasyPassword = table.Column<string>(maxLength: 65535, nullable: true),
MustUpdatePassword = table.Column<bool>(nullable: false),
- AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: false),
+ AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
AuthenticationProviderId = table.Column<string>(maxLength: 255, nullable: false),
- GroupedFolders = table.Column<string>(maxLength: 65535, nullable: true),
+ PasswordResetProviderId = table.Column<string>(maxLength: 255, nullable: false),
InvalidLoginAttemptCount = table.Column<int>(nullable: false),
- LatestItemExcludes = table.Column<string>(maxLength: 65535, nullable: true),
+ LastActivityDate = table.Column<DateTime>(nullable: false),
+ LastLoginDate = table.Column<DateTime>(nullable: false),
LoginAttemptsBeforeLockout = table.Column<int>(nullable: true),
- MyMediaExcludes = table.Column<string>(maxLength: 65535, nullable: true),
- OrderedViews = table.Column<string>(maxLength: 65535, nullable: true),
- SubtitleMode = table.Column<string>(maxLength: 255, nullable: false),
+ SubtitleMode = table.Column<int>(nullable: false),
PlayDefaultAudioTrack = table.Column<bool>(nullable: false),
- SubtitleLanguagePrefernce = table.Column<string>(maxLength: 255, nullable: true),
- DisplayMissingEpisodes = table.Column<bool>(nullable: true),
- DisplayCollectionsView = table.Column<bool>(nullable: true),
- HidePlayedInLatest = table.Column<bool>(nullable: true),
- RememberAudioSelections = table.Column<bool>(nullable: true),
- RememberSubtitleSelections = table.Column<bool>(nullable: true),
- EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: true),
- EnableUserPreferenceAccess = table.Column<bool>(nullable: true),
+ SubtitleLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
+ DisplayMissingEpisodes = table.Column<bool>(nullable: false),
+ DisplayCollectionsView = table.Column<bool>(nullable: false),
+ EnableLocalPassword = table.Column<bool>(nullable: false),
+ HidePlayedInLatest = table.Column<bool>(nullable: false),
+ RememberAudioSelections = table.Column<bool>(nullable: false),
+ RememberSubtitleSelections = table.Column<bool>(nullable: false),
+ EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: false),
+ EnableAutoLogin = table.Column<bool>(nullable: false),
+ EnableUserPreferenceAccess = table.Column<bool>(nullable: false),
+ MaxParentalAgeRating = table.Column<int>(nullable: true),
+ RemoteClientBitrateLimit = table.Column<int>(nullable: true),
+ InternalId = table.Column<long>(nullable: false),
+ ProfileImageId = table.Column<int>(nullable: true),
RowVersion = table.Column<uint>(nullable: false)
},
constraints: table =>
{
- table.PrimaryKey("PK_User", x => x.Id);
+ table.PrimaryKey("PK_Users", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Users_ImageInfo_ProfileImageId",
+ column: x => x.ProfileImageId,
+ principalSchema: "jellyfin",
+ principalTable: "ImageInfo",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
- name: "Group",
+ name: "AccessSchedule",
schema: "jellyfin",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column<Guid>(nullable: false),
+ DayOfWeek = table.Column<int>(nullable: false),
+ StartHour = table.Column<double>(nullable: false),
+ EndHour = table.Column<double>(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AccessSchedule", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AccessSchedule_Users_UserId",
+ column: x => x.UserId,
+ principalSchema: "jellyfin",
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Groups",
+ schema: "jellyfin",
+ columns: table => new
+ {
+ Id = table.Column<Guid>(nullable: false),
Name = table.Column<string>(maxLength: 255, nullable: false),
RowVersion = table.Column<uint>(nullable: false),
- Group_Groups_Id = table.Column<int>(nullable: true)
+ Group_Groups_Guid = table.Column<Guid>(nullable: true)
},
constraints: table =>
{
- table.PrimaryKey("PK_Group", x => x.Id);
+ table.PrimaryKey("PK_Groups", x => x.Id);
table.ForeignKey(
- name: "FK_Group_User_Group_Groups_Id",
- column: x => x.Group_Groups_Id,
+ name: "FK_Groups_Users_Group_Groups_Guid",
+ column: x => x.Group_Groups_Guid,
principalSchema: "jellyfin",
- principalTable: "User",
+ principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
- name: "Permission",
+ name: "Permissions",
schema: "jellyfin",
columns: table => new
{
@@ -77,30 +128,30 @@ namespace Jellyfin.Server.Implementations.Migrations
Kind = table.Column<int>(nullable: false),
Value = table.Column<bool>(nullable: false),
RowVersion = table.Column<uint>(nullable: false),
- Permission_GroupPermissions_Id = table.Column<int>(nullable: true),
- Permission_Permissions_Id = table.Column<int>(nullable: true)
+ Permission_GroupPermissions_Id = table.Column<Guid>(nullable: true),
+ Permission_Permissions_Guid = table.Column<Guid>(nullable: true)
},
constraints: table =>
{
- table.PrimaryKey("PK_Permission", x => x.Id);
+ table.PrimaryKey("PK_Permissions", x => x.Id);
table.ForeignKey(
- name: "FK_Permission_Group_Permission_GroupPermissions_Id",
+ name: "FK_Permissions_Groups_Permission_GroupPermissions_Id",
column: x => x.Permission_GroupPermissions_Id,
principalSchema: "jellyfin",
- principalTable: "Group",
+ principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
- name: "FK_Permission_User_Permission_Permissions_Id",
- column: x => x.Permission_Permissions_Id,
+ name: "FK_Permissions_Users_Permission_Permissions_Guid",
+ column: x => x.Permission_Permissions_Guid,
principalSchema: "jellyfin",
- principalTable: "User",
+ principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
- name: "Preference",
+ name: "Preferences",
schema: "jellyfin",
columns: table => new
{
@@ -109,23 +160,24 @@ namespace Jellyfin.Server.Implementations.Migrations
Kind = table.Column<int>(nullable: false),
Value = table.Column<string>(maxLength: 65535, nullable: false),
RowVersion = table.Column<uint>(nullable: false),
- Preference_Preferences_Id = table.Column<int>(nullable: true)
+ Preference_Preferences_Guid = table.Column<Guid>(nullable: true),
+ Preference_Preferences_Id = table.Column<Guid>(nullable: true)
},
constraints: table =>
{
- table.PrimaryKey("PK_Preference", x => x.Id);
+ table.PrimaryKey("PK_Preferences", x => x.Id);
table.ForeignKey(
- name: "FK_Preference_Group_Preference_Preferences_Id",
- column: x => x.Preference_Preferences_Id,
+ name: "FK_Preferences_Users_Preference_Preferences_Guid",
+ column: x => x.Preference_Preferences_Guid,
principalSchema: "jellyfin",
- principalTable: "Group",
+ principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
- name: "FK_Preference_User_Preference_Preferences_Id",
+ name: "FK_Preferences_Groups_Preference_Preferences_Id",
column: x => x.Preference_Preferences_Id,
principalSchema: "jellyfin",
- principalTable: "User",
+ principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
@@ -141,49 +193,61 @@ namespace Jellyfin.Server.Implementations.Migrations
ProviderSecrets = table.Column<string>(maxLength: 65535, nullable: false),
ProviderData = table.Column<string>(maxLength: 65535, nullable: false),
RowVersion = table.Column<uint>(nullable: false),
- ProviderMapping_ProviderMappings_Id = table.Column<int>(nullable: true)
+ ProviderMapping_ProviderMappings_Id = table.Column<Guid>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ProviderMapping", x => x.Id);
table.ForeignKey(
- name: "FK_ProviderMapping_Group_ProviderMapping_ProviderMappings_Id",
+ name: "FK_ProviderMapping_Groups_ProviderMapping_ProviderMappings_Id",
column: x => x.ProviderMapping_ProviderMappings_Id,
principalSchema: "jellyfin",
- principalTable: "Group",
+ principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
- name: "FK_ProviderMapping_User_ProviderMapping_ProviderMappings_Id",
+ name: "FK_ProviderMapping_Users_ProviderMapping_ProviderMappings_Id",
column: x => x.ProviderMapping_ProviderMappings_Id,
principalSchema: "jellyfin",
- principalTable: "User",
+ principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
- name: "IX_Group_Group_Groups_Id",
+ name: "IX_AccessSchedule_UserId",
schema: "jellyfin",
- table: "Group",
- column: "Group_Groups_Id");
+ table: "AccessSchedule",
+ column: "UserId");
migrationBuilder.CreateIndex(
- name: "IX_Permission_Permission_GroupPermissions_Id",
+ name: "IX_Groups_Group_Groups_Guid",
schema: "jellyfin",
- table: "Permission",
+ table: "Groups",
+ column: "Group_Groups_Guid");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Permissions_Permission_GroupPermissions_Id",
+ schema: "jellyfin",
+ table: "Permissions",
column: "Permission_GroupPermissions_Id");
migrationBuilder.CreateIndex(
- name: "IX_Permission_Permission_Permissions_Id",
+ name: "IX_Permissions_Permission_Permissions_Guid",
+ schema: "jellyfin",
+ table: "Permissions",
+ column: "Permission_Permissions_Guid");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Preferences_Preference_Preferences_Guid",
schema: "jellyfin",
- table: "Permission",
- column: "Permission_Permissions_Id");
+ table: "Preferences",
+ column: "Preference_Preferences_Guid");
migrationBuilder.CreateIndex(
- name: "IX_Preference_Preference_Preferences_Id",
+ name: "IX_Preferences_Preference_Preferences_Id",
schema: "jellyfin",
- table: "Preference",
+ table: "Preferences",
column: "Preference_Preferences_Id");
migrationBuilder.CreateIndex(
@@ -191,16 +255,26 @@ namespace Jellyfin.Server.Implementations.Migrations
schema: "jellyfin",
table: "ProviderMapping",
column: "ProviderMapping_ProviderMappings_Id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Users_ProfileImageId",
+ schema: "jellyfin",
+ table: "Users",
+ column: "ProfileImageId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
- name: "Permission",
+ name: "AccessSchedule",
+ schema: "jellyfin");
+
+ migrationBuilder.DropTable(
+ name: "Permissions",
schema: "jellyfin");
migrationBuilder.DropTable(
- name: "Preference",
+ name: "Preferences",
schema: "jellyfin");
migrationBuilder.DropTable(
@@ -208,11 +282,15 @@ namespace Jellyfin.Server.Implementations.Migrations
schema: "jellyfin");
migrationBuilder.DropTable(
- name: "Group",
+ name: "Groups",
+ schema: "jellyfin");
+
+ migrationBuilder.DropTable(
+ name: "Users",
schema: "jellyfin");
migrationBuilder.DropTable(
- name: "User",
+ name: "ImageInfo",
schema: "jellyfin");
}
}
diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
index 0fb0ba803..46714e865 100644
--- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
+++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
@@ -1,7 +1,9 @@
// <auto-generated />
using System;
+using Jellyfin.Server.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
@@ -15,6 +17,31 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.3");
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property<double>("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property<double>("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedule");
+ });
+
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
@@ -63,12 +90,12 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
{
- b.Property<int>("Id")
+ b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
+ .HasColumnType("TEXT");
- b.Property<int?>("Group_Groups_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Group_Groups_Guid")
+ .HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
@@ -81,9 +108,27 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.HasIndex("Group_Groups_Id");
+ b.HasIndex("Group_Groups_Guid");
+
+ b.ToTable("Groups");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
- b.ToTable("Group");
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ImageInfo");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@@ -95,11 +140,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<int>("Kind")
.HasColumnType("INTEGER");
- b.Property<int?>("Permission_GroupPermissions_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Permission_GroupPermissions_Id")
+ .HasColumnType("TEXT");
- b.Property<int?>("Permission_Permissions_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Permission_Permissions_Guid")
+ .HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
@@ -112,9 +157,9 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("Permission_GroupPermissions_Id");
- b.HasIndex("Permission_Permissions_Id");
+ b.HasIndex("Permission_Permissions_Guid");
- b.ToTable("Permission");
+ b.ToTable("Permissions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
@@ -126,8 +171,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<int>("Kind")
.HasColumnType("INTEGER");
- b.Property<int?>("Preference_Preferences_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("Preference_Preferences_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<Guid?>("Preference_Preferences_Id")
+ .HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
@@ -140,9 +188,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
+ b.HasIndex("Preference_Preferences_Guid");
+
b.HasIndex("Preference_Preferences_Id");
- b.ToTable("Preference");
+ b.ToTable("Preferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
@@ -156,8 +206,8 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT")
.HasMaxLength(65535);
- b.Property<int?>("ProviderMapping_ProviderMappings_Id")
- .HasColumnType("INTEGER");
+ b.Property<Guid?>("ProviderMapping_ProviderMappings_Id")
+ .HasColumnType("TEXT");
b.Property<string>("ProviderName")
.IsRequired()
@@ -182,12 +232,11 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
- b.Property<int>("Id")
+ b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
+ .HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
- .IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
@@ -196,71 +245,86 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT")
.HasMaxLength(255);
- b.Property<bool?>("DisplayCollectionsView")
+ b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
- b.Property<bool?>("DisplayMissingEpisodes")
+ b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
- b.Property<bool?>("EnableNextEpisodeAutoPlay")
+ b.Property<string>("EasyPassword")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
- b.Property<bool?>("EnableUserPreferenceAccess")
+ b.Property<bool>("EnableLocalPassword")
.HasColumnType("INTEGER");
- b.Property<string>("GroupedFolders")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<bool>("EnableNextEpisodeAutoPlay")
+ .HasColumnType("INTEGER");
- b.Property<bool?>("HidePlayedInLatest")
+ b.Property<bool>("EnableUserPreferenceAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("HidePlayedInLatest")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("InternalId")
.HasColumnType("INTEGER");
b.Property<int>("InvalidLoginAttemptCount")
.HasColumnType("INTEGER");
- b.Property<string>("LatestItemExcludes")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<DateTime>("LastActivityDate")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastLoginDate")
+ .HasColumnType("TEXT");
b.Property<int?>("LoginAttemptsBeforeLockout")
.HasColumnType("INTEGER");
- b.Property<bool>("MustUpdatePassword")
+ b.Property<int?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
- b.Property<string>("MyMediaExcludes")
- .HasColumnType("TEXT")
- .HasMaxLength(65535);
+ b.Property<bool>("MustUpdatePassword")
+ .HasColumnType("INTEGER");
- b.Property<string>("OrderedViews")
+ b.Property<string>("Password")
.HasColumnType("TEXT")
.HasMaxLength(65535);
- b.Property<string>("Password")
+ b.Property<string>("PasswordResetProviderId")
+ .IsRequired()
.HasColumnType("TEXT")
- .HasMaxLength(65535);
+ .HasMaxLength(255);
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
- b.Property<bool?>("RememberAudioSelections")
+ b.Property<int?>("ProfileImageId")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberAudioSelections")
.HasColumnType("INTEGER");
- b.Property<bool?>("RememberSubtitleSelections")
+ b.Property<bool>("RememberSubtitleSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("RemoteClientBitrateLimit")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
- b.Property<string>("SubtitleLanguagePrefernce")
+ b.Property<string>("SubtitleLanguagePreference")
.HasColumnType("TEXT")
.HasMaxLength(255);
- b.Property<string>("SubtitleMode")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasMaxLength(255);
+ b.Property<int>("SubtitleMode")
+ .HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
@@ -269,34 +333,45 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.ToTable("User");
+ b.HasIndex("ProfileImageId");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("AccessSchedules")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Groups")
- .HasForeignKey("Group_Groups_Id");
+ .HasForeignKey("Group_Groups_Guid");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.HasOne("Jellyfin.Data.Entities.Group", null)
- .WithMany("GroupPermissions")
+ .WithMany("Permissions")
.HasForeignKey("Permission_GroupPermissions_Id");
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Permissions")
- .HasForeignKey("Permission_Permissions_Id");
+ .HasForeignKey("Permission_Permissions_Guid");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
- b.HasOne("Jellyfin.Data.Entities.Group", null)
+ b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Preferences")
- .HasForeignKey("Preference_Preferences_Id");
+ .HasForeignKey("Preference_Preferences_Guid");
- b.HasOne("Jellyfin.Data.Entities.User", null)
+ b.HasOne("Jellyfin.Data.Entities.Group", null)
.WithMany("Preferences")
.HasForeignKey("Preference_Preferences_Id");
});
@@ -311,6 +386,13 @@ namespace Jellyfin.Server.Implementations.Migrations
.WithMany("ProviderMappings")
.HasForeignKey("ProviderMapping_ProviderMappings_Id");
});
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage")
+ .WithMany()
+ .HasForeignKey("ProfileImageId");
+ });
#pragma warning restore 612, 618
}
}
diff --git a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
index 024500bf8..38494727c 100644
--- a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Common.Cryptography;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Model.Cryptography;
-namespace Jellyfin.Server.Implementations.User
+namespace Jellyfin.Server.Implementations.Users
{
/// <summary>
/// The default authentication provider.
diff --git a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
index 80ab3ce00..60b48ec76 100644
--- a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
@@ -10,7 +11,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
-namespace Jellyfin.Server.Implementations.User
+namespace Jellyfin.Server.Implementations.Users
{
/// <summary>
/// The default password reset provider.
@@ -94,7 +95,7 @@ namespace Jellyfin.Server.Implementations.User
}
/// <inheritdoc />
- public async Task<ForgotPasswordResult> StartForgotPasswordProcess(Jellyfin.Data.Entities.User user, bool isInNetwork)
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork)
{
string pin;
using (var cryptoRandom = RandomNumberGenerator.Create())
diff --git a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs
index d33034ab2..d94a27b9d 100644
--- a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs
+++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
@@ -9,7 +10,7 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
-namespace Jellyfin.Server.Implementations.User
+namespace Jellyfin.Server.Implementations.Users
{
public sealed class DeviceAccessEntryPoint : IServerEntryPoint
{
@@ -33,7 +34,11 @@ namespace Jellyfin.Server.Implementations.User
return Task.CompletedTask;
}
- private void OnUserUpdated(object sender, GenericEventArgs<Data.Entities.User> e)
+ public void Dispose()
+ {
+ }
+
+ private void OnUserUpdated(object sender, GenericEventArgs<User> e)
{
var user = e.Argument;
if (!user.HasPermission(PermissionKind.EnableAllDevices))
@@ -42,11 +47,7 @@ namespace Jellyfin.Server.Implementations.User
}
}
- public void Dispose()
- {
- }
-
- private void UpdateDeviceAccess(Data.Entities.User user)
+ private void UpdateDeviceAccess(User user)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
{
diff --git a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
index a11ca128a..e430808bf 100644
--- a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
-namespace Jellyfin.Server.Implementations.User
+namespace Jellyfin.Server.Implementations.Users
{
/// <summary>
/// An invalid authentication provider.
diff --git a/Jellyfin.Server.Implementations/User/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 73905ff70..ddc05055b 100644
--- a/Jellyfin.Server.Implementations/User/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS0067
+#pragma warning disable CA1307
#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.Users
{
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().FullName);
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(),
- AuthenticatioIsnProviderId = user.AuthenticationProviderId,
+ AuthenticationProviderId = 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 UpdateUserAsync(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.AuthenticatioIsnProviderId;
+ user.AuthenticationProviderId = policy.AuthenticationProviderId;
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 {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected",
+ "User {Username} 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 {UserName} due to {Attempts} unsuccessful login attempts.",
+ "Disabling user {Username} due to {Attempts} unsuccessful login attempts.",
user.Username,
invalidLogins);
}
UpdateUser(user);
}
-
- private void ResetInvalidLoginAttemptCount(Data.Entities.User user)
- {
- user.InvalidLoginAttemptCount = 0;
- }
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 331a32c73..b4c5fd4ea 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -7,8 +7,10 @@ using Emby.Server.Implementations;
using Jellyfin.Drawing.Skia;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
+using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using Microsoft.EntityFrameworkCore;
@@ -69,6 +71,7 @@ namespace Jellyfin.Server
serviceCollection.AddSingleton<JellyfinDbProvider>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
+ serviceCollection.AddSingleton<IUserManager, UserManager>();
base.RegisterServices(serviceCollection);
}
@@ -80,6 +83,9 @@ namespace Jellyfin.Server
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
{
yield return typeof(CoreAppHost).Assembly;
+ yield return typeof(DefaultAuthenticationProvider).Assembly;
+ yield return typeof(DefaultPasswordResetProvider).Assembly;
+ yield return typeof(InvalidAuthProvider).Assembly;
}
/// <inheritdoc />
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index ea16c5573..9eec6ed4e 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -41,7 +41,6 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" />
- <PackageReference Include="Json.Net" Version="1.0.22" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
<PackageReference Include="prometheus-net" Version="3.5.0" />
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index b19d7f7fc..0271098f4 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -1,33 +1,45 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
+using System;
using System.IO;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Controller;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Users;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
using SQLitePCL.pretty;
+using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Jellyfin.Server.Migrations.Routines
{
+ /// <summary>
+ /// The migration routine for migrating the user database to EF Core.
+ /// </summary>
public class MigrateUserDb : IMigrationRoutine
{
- private readonly ILogger<MigrateUserDb> _logger;
+ private const string DbFilename = "users.db";
+ private readonly ILogger<MigrateUserDb> _logger;
private readonly IServerApplicationPaths _paths;
-
private readonly JellyfinDbProvider _provider;
-
private readonly MyXmlSerializer _xmlSerializer;
- public MigrateUserDb(ILogger<MigrateUserDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider, MyXmlSerializer xmlSerializer)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="paths">The server application paths.</param>
+ /// <param name="provider">The database provider.</param>
+ /// <param name="xmlSerializer">The xml serializer.</param>
+ public MigrateUserDb(
+ ILogger<MigrateUserDb> logger,
+ IServerApplicationPaths paths,
+ JellyfinDbProvider provider,
+ MyXmlSerializer xmlSerializer)
{
_logger = logger;
_paths = paths;
@@ -35,18 +47,21 @@ namespace Jellyfin.Server.Migrations.Routines
_xmlSerializer = xmlSerializer;
}
+ /// <inheritdoc/>
public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C");
- public string Name => "MigrateUserDb";
+ /// <inheritdoc/>
+ public string Name => "MigrateUserDatabase";
+ /// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
- using (var connection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null))
+ using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null))
{
- using var dbContext = _provider.CreateContext();
+ var dbContext = _provider.CreateContext();
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
@@ -55,26 +70,30 @@ namespace Jellyfin.Server.Migrations.Routines
foreach (var entry in queryResult)
{
- var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(entry[2].ToString());
- var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, json["Name"]);
-
- var config = (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"));
- var policy = (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"));
-
- var user = new User(
- json["Name"],
- false,
- policy.AuthenticatioIsnProviderId,
- policy.InvalidLoginAttemptCount,
- config.SubtitleMode,
- config.PlayDefaultAudioTrack)
+ UserMockup mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob());
+ var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
+
+ var config = File.Exists(Path.Combine(userDataDir, "config.xml"))
+ ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"))
+ : new UserConfiguration();
+ var policy = File.Exists(Path.Combine(userDataDir, "policy.xml"))
+ ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"))
+ : new UserPolicy();
+ policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
+ "Emby.Server.Implementations.Library",
+ "Jellyfin.Server.Implementations.Users",
+ StringComparison.Ordinal)
+ ?? typeof(DefaultAuthenticationProvider).FullName;
+
+ policy.PasswordResetProviderId ??= typeof(DefaultPasswordResetProvider).FullName;
+
+ var user = new User(mockup.Name, policy.AuthenticationProviderId)
{
Id = entry[1].ReadGuidFromBlob(),
InternalId = entry[0].ToInt64(),
MaxParentalAgeRating = policy.MaxParentalRating,
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
- AuthenticationProviderId = policy.AuthenticatioIsnProviderId,
PasswordResetProviderId = policy.PasswordResetProviderId,
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout),
@@ -89,6 +108,10 @@ namespace Jellyfin.Server.Migrations.Routines
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
RememberSubtitleSelections = config.RememberSubtitleSelections,
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
+ Password = mockup.Password,
+ EasyPassword = mockup.EasyPassword,
+ LastLoginDate = mockup.LastLoginDate ?? DateTime.MinValue,
+ LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue
};
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
@@ -112,6 +135,7 @@ namespace Jellyfin.Server.Migrations.Routines
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
+
foreach (var policyAccessSchedule in policy.AccessSchedules)
{
user.AccessSchedules.Add(policyAccessSchedule);
@@ -126,6 +150,8 @@ namespace Jellyfin.Server.Migrations.Routines
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
+
+ dbContext.Users.Add(user);
}
dbContext.SaveChanges();
@@ -133,12 +159,32 @@ namespace Jellyfin.Server.Migrations.Routines
try
{
- File.Move(Path.Combine(dataPath, "users.db"), Path.Combine(dataPath, "users.db" + ".old"));
+ File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+
+ var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
+ if (File.Exists(journalPath))
+ {
+ File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
+ }
}
catch (IOException e)
{
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
}
}
+
+#nullable disable
+ internal class UserMockup
+ {
+ public string Password { get; set; }
+
+ public string EasyPassword { get; set; }
+
+ public DateTime? LastLoginDate { get; set; }
+
+ public DateTime? LastActivityDate { get; set; }
+
+ public string Name { get; set; }
+ }
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 8d94d3971..bc9724027 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -1,6 +1,8 @@
#pragma warning disable CS1591
using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -34,7 +36,7 @@ namespace MediaBrowser.Model.Users
public string[] BlockedTags { get; set; }
public bool EnableUserPreferenceAccess { get; set; }
- public Jellyfin.Data.Entities.AccessSchedule[] AccessSchedules { get; set; }
+ public AccessSchedule[] AccessSchedules { get; set; }
public UnratedItem[] BlockUnratedItems { get; set; }
public bool EnableRemoteControlOfOtherUsers { get; set; }
public bool EnableSharedDeviceControl { get; set; }
@@ -78,7 +80,9 @@ namespace MediaBrowser.Model.Users
public string[] BlockedChannels { get; set; }
public int RemoteClientBitrateLimit { get; set; }
- public string AuthenticatioIsnProviderId { get; set; }
+
+ [XmlElement(ElementName = "AuthenticationProviderId")]
+ public string AuthenticationProviderId { get; set; }
public string PasswordResetProviderId { get; set; }
public UserPolicy()