aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorLogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com>2019-08-06 00:26:19 -0700
committerGitHub <noreply@github.com>2019-08-06 00:26:19 -0700
commit984e415c66cbd995d12ea95a3a9d3e2561ce4869 (patch)
tree1799942f3836641786c0e29249801bdb46aac0f4 /Emby.Server.Implementations/Library
parentc2667f99f4d50f4f7d9bbeec50e8491e52468962 (diff)
parent89f592687ee7ae7f0e0fffd884dbf2890476410a (diff)
Merge pull request #5 from jellyfin/master
Merge up to latest master
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs78
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs34
-rw-r--r--Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs132
-rw-r--r--Emby.Server.Implementations/Library/InvalidAuthProvider.cs47
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs38
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs52
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs314
10 files changed, 403 insertions, 300 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index c644d13ea..f1ae2fc9c 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,10 +1,10 @@
using System;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
@@ -16,12 +16,10 @@ namespace Emby.Server.Implementations.Library
{
private readonly ILibraryManager _libraryManager;
- private bool _ignoreDotPrefix;
-
/// <summary>
- /// Any folder named in this list will be ignored - can be added to at runtime for extensibility
+ /// Any folder named in this list will be ignored
/// </summary>
- public static readonly string[] IgnoreFolders =
+ private static readonly string[] _ignoreFolders =
{
"metadata",
"ps3_update",
@@ -42,25 +40,14 @@ namespace Emby.Server.Implementations.Library
"$RECYCLE.BIN",
"System Volume Information",
".grab",
-
- // macos
- ".AppleDouble"
-
};
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
-
- _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
}
- /// <summary>
- /// Shoulds the ignore.
- /// </summary>
- /// <param name="fileInfo">The file information.</param>
- /// <param name="parent">The parent.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
{
// Don't ignore top level folders
@@ -72,46 +59,17 @@ namespace Emby.Server.Implementations.Library
var filename = fileInfo.Name;
var path = fileInfo.FullName;
- // Handle mac .DS_Store
- // https://github.com/MediaBrowser/MediaBrowser/issues/427
- if (_ignoreDotPrefix)
+ // Ignore hidden files on UNIX
+ if (Environment.OSVersion.Platform != PlatformID.Win32NT
+ && filename[0] == '.')
{
- if (filename.IndexOf('.') == 0)
- {
- return true;
- }
+ return true;
}
- // Ignore hidden files and folders
- //if (fileInfo.IsHidden)
- //{
- // if (parent == null)
- // {
- // var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path));
-
- // if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
- // {
- // return false;
- // }
- // if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
- // {
- // return false;
- // }
- // }
-
- // // Sometimes these are marked hidden
- // if (_fileSystem.IsRootPath(path))
- // {
- // return false;
- // }
-
- // return true;
- //}
-
if (fileInfo.IsDirectory)
{
// Ignore any folders in our list
- if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
+ if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
@@ -119,8 +77,9 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
- if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
- !(parent is AggregateFolder) && !(parent is UserRootFolder))
+ if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)
+ && !(parent is AggregateFolder)
+ && !(parent is UserRootFolder))
{
return true;
}
@@ -141,22 +100,17 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Don't resolve these into audio files
- if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
+ if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename)
+ && _libraryManager.IsAudioFile(filename))
{
return true;
}
}
// Ignore samples
- var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("-", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("_", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("!", " ", StringComparison.OrdinalIgnoreCase);
+ Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
- if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
+ return m.Success;
}
return false;
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index 3d15a8afb..fe09b07ff 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Library
public string Name => "Default";
public bool IsEnabled => true;
-
+
// This is dumb and an artifact of the backwards way auth providers were designed.
// This version of authenticate was never meant to be called, but needs to be here for interface compat
// Only the providers that don't provide local user support use this
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library
{
throw new NotImplementedException();
}
-
+
// This is the verson that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
string hash = user.Password;
user.Password = string.Format("$SHA1${0}", hash);
}
-
+
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
{
string hash = user.EasyPassword;
@@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library
return user.Password;
}
+ public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
+ {
+ ConvertPasswordFormat(user);
+
+ if (newPassword != null)
+ {
+ newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword));
+ }
+
+ if (string.IsNullOrWhiteSpace(newPasswordHash))
+ {
+ throw new ArgumentNullException(nameof(newPasswordHash));
+ }
+
+ user.EasyPassword = newPasswordHash;
+ }
+
+ public string GetEasyPasswordHash(User user)
+ {
+ // This should be removed in the future. This was added to let user login after
+ // Jellyfin 10.3.3 failed to save a well formatted PIN.
+ ConvertPasswordFormat(user);
+
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : (new PasswordHash(user.EasyPassword)).Hash;
+ }
+
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
{
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
new file mode 100644
index 000000000..e218749d9
--- /dev/null
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ public string Name => "Default Password Reset Provider";
+
+ public bool IsEnabled => true;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+ private readonly string _passwordResetFileBaseName = "passwordreset";
+
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
+ private readonly ICryptoProvider _crypto;
+
+ public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _jsonSerializer = jsonSerializer;
+ _userManager = userManager;
+ _crypto = cryptoProvider;
+ }
+
+ public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+ {
+ SerializablePasswordReset spr;
+ HashSet<string> usersreset = new HashSet<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ {
+ using (var str = File.OpenRead(resetfile))
+ {
+ spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.Now)
+ {
+ File.Delete(resetfile);
+ }
+ else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = _userManager.GetUserByName(spr.UserName);
+ if (resetUser == null)
+ {
+ throw new Exception($"User with a username of {spr.UserName} not found");
+ }
+
+ await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersreset.Add(resetUser.Name);
+ File.Delete(resetfile);
+ }
+ }
+
+ if (usersreset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+ else
+ {
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersreset.ToArray()
+ };
+ }
+ }
+
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ {
+ string pin = string.Empty;
+ using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.Now.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.InternalId + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Name
+ };
+
+ try
+ {
+ using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ _jsonSerializer.SerializeToStream(spr, fileStream);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
+ }
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ PinFile = filePath
+ };
+ }
+
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
new file mode 100644
index 000000000..25d233137
--- /dev/null
+++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Net;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class InvalidAuthProvider : IAuthenticationProvider
+ {
+ public string Name => "InvalidOrMissingAuthenticationProvider";
+
+ public bool IsEnabled => true;
+
+ public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
+ {
+ throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
+ }
+
+ public Task<bool> HasPassword(User user)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task ChangePassword(User user, string newPassword)
+ {
+ return Task.CompletedTask;
+ }
+
+ public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
+ {
+ // Nothing here
+ }
+
+ public string GetPasswordHash(User user)
+ {
+ return string.Empty;
+ }
+
+ public string GetEasyPasswordHash(User user)
+ {
+ return string.Empty;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 1673e3777..4b5063ada 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2368,7 +2368,7 @@ namespace Emby.Server.Implementations.Library
public int? GetSeasonNumberFromPath(string path)
{
- return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber;
+ return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
}
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 848563679..1b63b00a3 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using Emby.Naming.Video;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@@ -11,7 +12,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.Movies
@@ -27,7 +27,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <value>The priority.</value>
public override ResolverPriority Priority => ResolverPriority.Third;
- public MultiItemResolverResult ResolveMultiple(Folder parent,
+ public MultiItemResolverResult ResolveMultiple(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -45,7 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
- private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
+ private MultiItemResolverResult ResolveMultipleInternal(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -90,7 +92,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
+ private MultiItemResolverResult ResolveVideos<T>(
+ Folder parent,
+ IEnumerable<FileSystemMetadata> fileSystemEntries,
+ IDirectoryService directoryService,
+ bool suppportMultiEditions,
+ string collectionType,
+ bool parseName)
where T : Video, new()
{
var files = new List<FileSystemMetadata>();
@@ -103,8 +111,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
if (string.IsNullOrEmpty(collectionType))
{
- if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
{
return null;
}
@@ -114,11 +122,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
leftOver.Add(child);
}
- else if (IsIgnored(child.Name))
- {
-
- }
- else
+ else if (!IsIgnored(child.Name))
{
files.Add(child);
}
@@ -167,17 +171,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static bool IsIgnored(string filename)
{
// Ignore samples
- var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("-", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("_", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("!", " ", StringComparison.OrdinalIgnoreCase);
-
- if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
+ Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
- return false;
+ return m.Success;
}
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index db270c398..8171c010b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -14,6 +14,18 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
+ private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "folder",
+ "thumb",
+ "landscape",
+ "fanart",
+ "backdrop",
+ "poster",
+ "cover",
+ "logo",
+ "default"
+ };
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
@@ -31,10 +43,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
if (!args.IsDirectory)
{
// Must be an image file within a photo collection
- var collectionType = args.GetCollectionType();
+ var collectionType = args.CollectionType;
- if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
- (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
{
if (IsImageFile(args.Path, _imageProcessor))
{
@@ -74,43 +86,29 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
+
+ internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
{
- if (imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase))
+ if (path == null)
{
- return true;
+ throw new ArgumentNullException(nameof(path));
}
- return false;
- }
-
- private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
- {
- "folder",
- "thumb",
- "landscape",
- "fanart",
- "backdrop",
- "poster",
- "cover",
- "logo",
- "default"
- };
-
- internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
- {
- var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
+ var filename = Path.GetFileNameWithoutExtension(path);
- if (IgnoreFiles.Contains(filename))
+ if (_ignoreFiles.Contains(filename))
{
return false;
}
- if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
+ if (_ignoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
{
return false;
}
- return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
+ string extension = Path.GetExtension(path).TrimStart('.');
+ return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index ce1386e91..3b9e48d97 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var path = args.Path;
- var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true);
+ var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
var season = new Season
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 5c95534ec..1f873d7c6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -194,9 +194,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
{
- var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
-
- var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber;
+ var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 4cf703add..1701ced42 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -79,6 +79,11 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
+ private InvalidAuthProvider _invalidAuthProvider;
+
+ private IPasswordResetProvider[] _passwordResetProviders;
+ private DefaultPasswordResetProvider _defaultPasswordResetProvider;
+
public UserManager(
ILoggerFactory loggerFactory,
IServerConfigurationManager configurationManager,
@@ -102,8 +107,6 @@ namespace Emby.Server.Implementations.Library
_fileSystem = fileSystem;
ConfigurationManager = configurationManager;
_users = Array.Empty<User>();
-
- DeletePinFile();
}
public NameIdPair[] GetAuthenticationProviders()
@@ -120,11 +123,31 @@ namespace Emby.Server.Implementations.Library
.ToArray();
}
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders)
+ public NameIdPair[] GetPasswordResetProviders()
+ {
+ return _passwordResetProviders
+ .Where(i => i.IsEnabled)
+ .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1)
+ .ThenBy(i => i.Name)
+ .Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = GetPasswordResetProviderId(i)
+ })
+ .ToArray();
+ }
+
+ 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();
}
#region UserUpdated Event
@@ -199,9 +222,8 @@ namespace Emby.Server.Implementations.Library
public void Initialize()
{
- _users = LoadUsers();
-
- var users = Users.ToList();
+ var users = LoadUsers();
+ _users = users.ToArray();
// If there are no local users with admin rights, make them all admins
if (!users.Any(i => i.Policy.IsAdministrator))
@@ -258,27 +280,37 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var success = false;
+ string updatedUsername = null;
IAuthenticationProvider authenticationProvider = null;
if (user != null)
{
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
authenticationProvider = authResult.Item1;
- success = authResult.Item2;
+ updatedUsername = authResult.Item2;
+ success = authResult.Item3;
}
else
{
// user is null
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
authenticationProvider = authResult.Item1;
- success = authResult.Item2;
+ updatedUsername = authResult.Item2;
+ success = authResult.Item3;
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
{
- user = await CreateUser(username).ConfigureAwait(false);
+ // We should trust the user that the authprovider says, not what was typed
+ if (updatedUsername != username)
+ {
+ username = updatedUsername;
+ }
+
+ // Search the database for the user again; the authprovider might have created it
+ user = Users
+ .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
- var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy;
- if (hasNewUserPolicy != null)
+ if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
var policy = hasNewUserPolicy.GetNewUserPolicy();
UpdateUserPolicy(user, policy, true);
@@ -342,11 +374,21 @@ namespace Emby.Server.Implementations.Library
return provider.GetType().FullName;
}
+ private static string GetPasswordResetProviderId(IPasswordResetProvider provider)
+ {
+ return provider.GetType().FullName;
+ }
+
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
return GetAuthenticationProviders(user).First();
}
+ private IPasswordResetProvider GetPasswordResetProvider(User user)
+ {
+ return GetPasswordResetProviders(user)[0];
+ }
+
private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{
var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
@@ -360,38 +402,67 @@ namespace Emby.Server.Implementations.Library
if (providers.Length == 0)
{
- providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider };
+ // 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.Name, user.Policy.AuthenticationProviderId);
+ providers = new IAuthenticationProvider[] { _invalidAuthProvider };
+ }
+
+ return providers;
+ }
+
+ private IPasswordResetProvider[] GetPasswordResetProviders(User user)
+ {
+ var passwordResetProviderId = user?.Policy.PasswordResetProviderId;
+
+ var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
+
+ if (!string.IsNullOrEmpty(passwordResetProviderId))
+ {
+ providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+
+ if (providers.Length == 0)
+ {
+ providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider };
}
return providers;
}
- private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
+ private async Task<Tuple<string, bool>> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
{
try
{
var requiresResolvedUser = provider as IRequiresResolvedUser;
+ ProviderAuthenticationResult authenticationResult = null;
if (requiresResolvedUser != null)
{
- await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
+ authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
}
else
{
- await provider.Authenticate(username, password).ConfigureAwait(false);
+ authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
+ }
+
+ if(authenticationResult.Username != username)
+ {
+ _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
+ username = authenticationResult.Username;
}
- return true;
+ return new Tuple<string, bool>(username, true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
- return false;
+ return new Tuple<string, bool>(username, false);
}
}
- private async Task<Tuple<IAuthenticationProvider, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
+ private async Task<Tuple<IAuthenticationProvider, string, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
{
+ string updatedUsername = null;
bool success = false;
IAuthenticationProvider authenticationProvider = null;
@@ -404,17 +475,20 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
foreach (var provider in GetAuthenticationProviders(user))
{
- success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
+ var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
+ updatedUsername = providerAuthResult.Item1;
+ success = providerAuthResult.Item2;
if (success)
{
authenticationProvider = provider;
+ username = updatedUsername;
break;
}
}
@@ -427,16 +501,16 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
- success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
}
}
}
- return new Tuple<IAuthenticationProvider, bool>(authenticationProvider, success);
+ return new Tuple<IAuthenticationProvider, string, bool>(authenticationProvider, username, success);
}
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
@@ -476,46 +550,40 @@ namespace Emby.Server.Implementations.Library
}
}
- private string GetLocalPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? null
- : user.EasyPassword;
- }
-
/// <summary>
/// Loads the users from the repository
/// </summary>
/// <returns>IEnumerable{User}.</returns>
- private User[] LoadUsers()
+ private List<User> LoadUsers()
{
var users = UserRepository.RetrieveAllUsers();
// There always has to be at least one user.
- if (users.Count == 0)
+ if (users.Count != 0)
{
- var defaultName = Environment.UserName;
- if (string.IsNullOrWhiteSpace(defaultName))
- {
- defaultName = "MyJellyfinUser";
- }
- var name = MakeValidUsername(defaultName);
+ return users;
+ }
- var user = InstantiateNewUser(name);
+ var defaultName = Environment.UserName;
+ if (string.IsNullOrWhiteSpace(defaultName))
+ {
+ defaultName = "MyJellyfinUser";
+ }
- user.DateLastSaved = DateTime.UtcNow;
+ var name = MakeValidUsername(defaultName);
- UserRepository.CreateUser(user);
+ var user = InstantiateNewUser(name);
- users.Add(user);
+ user.DateLastSaved = DateTime.UtcNow;
- user.Policy.IsAdministrator = true;
- user.Policy.EnableContentDeletion = true;
- user.Policy.EnableRemoteControlOfOtherUsers = true;
- UpdateUserPolicy(user, user.Policy, false);
- }
+ UserRepository.CreateUser(user);
- return users.ToArray();
+ user.Policy.IsAdministrator = true;
+ user.Policy.EnableContentDeletion = true;
+ user.Policy.EnableRemoteControlOfOtherUsers = true;
+ UpdateUserPolicy(user, user.Policy, false);
+
+ return new List<User> { user };
}
public UserDto GetUserDto(User user, string remoteEndPoint = null)
@@ -526,7 +594,7 @@ namespace Emby.Server.Implementations.Library
}
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
- bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user));
+ bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
hasConfiguredEasyPassword :
@@ -814,17 +882,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (newPassword != null)
- {
- newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.EasyPassword = newPasswordHash;
+ GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
UpdateUser(user);
@@ -844,159 +902,51 @@ namespace Emby.Server.Implementations.Library
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true,
- //Salt = BCrypt.GenerateSalt()
- };
- }
-
- private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt");
-
- private string _lastPin;
- private PasswordPinCreationResult _lastPasswordPinCreationResult;
- private int _pinAttempts;
-
- private async Task<PasswordPinCreationResult> CreatePasswordResetPin()
- {
- var num = new Random().Next(1, 9999);
-
- var path = PasswordResetFile;
-
- var pin = num.ToString("0000", CultureInfo.InvariantCulture);
- _lastPin = pin;
-
- var time = TimeSpan.FromMinutes(5);
- var expiration = DateTime.UtcNow.Add(time);
-
- var text = new StringBuilder();
-
- var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty;
-
- text.AppendLine("Use your web browser to visit:");
- text.AppendLine(string.Empty);
- text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html");
- text.AppendLine(string.Empty);
- text.AppendLine("Enter the following pin code:");
- text.AppendLine(string.Empty);
- text.AppendLine(pin);
- text.AppendLine(string.Empty);
-
- var localExpirationTime = expiration.ToLocalTime();
- // Tuesday, 22 August 2006 06:30 AM
- text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
-
- File.WriteAllText(path, text.ToString(), Encoding.UTF8);
-
- var result = new PasswordPinCreationResult
- {
- PinFile = path,
- ExpirationDate = expiration
+ UsesIdForConfigurationPath = true
};
-
- _lastPasswordPinCreationResult = result;
- _pinAttempts = 0;
-
- return result;
}
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
{
- DeletePinFile();
-
var user = string.IsNullOrWhiteSpace(enteredUsername) ?
null :
GetUserByName(enteredUsername);
var action = ForgotPasswordAction.InNetworkRequired;
- string pinFile = null;
- DateTime? expirationDate = null;
- if (user != null && !user.Policy.IsAdministrator)
+ if (user != null && isInNetwork)
{
- action = ForgotPasswordAction.ContactAdmin;
+ var passwordResetProvider = GetPasswordResetProvider(user);
+ return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false);
}
else
{
- if (isInNetwork)
+ return new ForgotPasswordResult
{
- action = ForgotPasswordAction.PinCode;
- }
-
- var result = await CreatePasswordResetPin().ConfigureAwait(false);
- pinFile = result.PinFile;
- expirationDate = result.ExpirationDate;
+ Action = action,
+ PinFile = string.Empty
+ };
}
-
- return new ForgotPasswordResult
- {
- Action = action,
- PinFile = pinFile,
- PinExpirationDate = expirationDate
- };
}
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
- DeletePinFile();
-
- var usersReset = new List<string>();
-
- var valid = !string.IsNullOrWhiteSpace(_lastPin) &&
- string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) &&
- _lastPasswordPinCreationResult != null &&
- _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow;
-
- if (valid)
- {
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
-
- foreach (var user in Users)
- {
- await ResetPassword(user).ConfigureAwait(false);
-
- if (user.Policy.IsDisabled)
- {
- user.Policy.IsDisabled = false;
- UpdateUserPolicy(user, user.Policy, true);
- }
- usersReset.Add(user.Name);
- }
- }
- else
+ foreach (var provider in _passwordResetProviders)
{
- _pinAttempts++;
- if (_pinAttempts >= 3)
+ var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
+ if (result.Success)
{
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
+ return result;
}
}
return new PinRedeemResult
{
- Success = valid,
- UsersReset = usersReset.ToArray()
+ Success = false,
+ UsersReset = Array.Empty<string>()
};
}
- private void DeletePinFile()
- {
- try
- {
- _fileSystem.DeleteFile(PasswordResetFile);
- }
- catch
- {
-
- }
- }
-
- class PasswordPinCreationResult
- {
- public string PinFile { get; set; }
- public DateTime ExpirationDate { get; set; }
- }
-
public UserPolicy GetUserPolicy(User user)
{
var path = GetPolicyFilePath(user);