mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-25 19:46:34 +00:00
fixes #941 - Rework password recovery and remove IsLocal checks
This commit is contained in:
@@ -1027,7 +1027,7 @@ namespace MediaBrowser.Server.Implementations.Connect
|
||||
{
|
||||
var user = e.Argument;
|
||||
|
||||
//await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
|
||||
await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task TryUploadUserPreferences(User user, CancellationToken cancellationToken)
|
||||
|
||||
@@ -129,6 +129,15 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
public ImageOutputFormat[] GetSupportedImageOutputFormats()
|
||||
{
|
||||
if (_webpAvailable)
|
||||
{
|
||||
return new[] { ImageOutputFormat.Webp, ImageOutputFormat.Gif, ImageOutputFormat.Jpg, ImageOutputFormat.Png };
|
||||
}
|
||||
return new[] { ImageOutputFormat.Gif, ImageOutputFormat.Jpg, ImageOutputFormat.Png };
|
||||
}
|
||||
|
||||
public async Task<string> ProcessImage(ImageProcessingOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
@@ -212,9 +221,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
||||
var newWidth = Convert.ToInt32(newSize.Width);
|
||||
var newHeight = Convert.ToInt32(newSize.Height);
|
||||
|
||||
var selectedOutputFormat = options.OutputFormat == ImageOutputFormat.Webp && !_webpAvailable
|
||||
? GetFallbackImageFormat(originalImagePath)
|
||||
: options.OutputFormat;
|
||||
var selectedOutputFormat = options.OutputFormat;
|
||||
|
||||
_logger.Debug("Processing image to {0}", selectedOutputFormat);
|
||||
|
||||
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
|
||||
// Also, Webp only supports Format32bppArgb and Format32bppRgb
|
||||
@@ -279,11 +288,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
private ImageOutputFormat GetFallbackImageFormat(string originalImagePath)
|
||||
{
|
||||
return ImageOutputFormat.Png;
|
||||
}
|
||||
|
||||
private void SaveToWebP(Bitmap thumbnail, Stream toStream, int quality)
|
||||
{
|
||||
new SimpleEncoder().Encode(thumbnail, toStream, quality);
|
||||
@@ -479,10 +483,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
||||
|
||||
filename += "datemodified=" + dateModified.Ticks;
|
||||
|
||||
if (format != ImageOutputFormat.Original)
|
||||
{
|
||||
filename += "f=" + format;
|
||||
}
|
||||
filename += "f=" + format;
|
||||
|
||||
var hasIndicator = false;
|
||||
|
||||
|
||||
@@ -63,17 +63,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
|
||||
// This code is executed before the service
|
||||
var auth = AuthorizationContext.GetAuthorizationInfo(req);
|
||||
|
||||
if (!authAttribtues.AllowLocal || !req.IsLocal)
|
||||
if (!string.IsNullOrWhiteSpace(auth.Token) ||
|
||||
!_config.Configuration.InsecureApps6.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(auth.Token) ||
|
||||
!_config.Configuration.InsecureApps6.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var valid = IsValidConnectKey(auth.Token);
|
||||
var valid = IsValidConnectKey(auth.Token);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
SessionManager.ValidateSecurityToken(auth.Token);
|
||||
}
|
||||
if (!valid)
|
||||
{
|
||||
SessionManager.ValidateSecurityToken(auth.Token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Connect;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
@@ -17,9 +18,11 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
using MediaBrowser.Server.Implementations.Security;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
@@ -65,7 +68,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
private readonly Func<IImageProcessor> _imageProcessorFactory;
|
||||
private readonly Func<IDtoService> _dtoServiceFactory;
|
||||
private readonly Func<IConnectManager> _connectFactory;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserManager" /> class.
|
||||
@@ -73,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="userRepository">The user repository.</param>
|
||||
public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IApplicationHost appHost)
|
||||
public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost)
|
||||
{
|
||||
_logger = logger;
|
||||
UserRepository = userRepository;
|
||||
@@ -85,6 +88,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
_appHost = appHost;
|
||||
ConfigurationManager = configurationManager;
|
||||
Users = new List<User>();
|
||||
|
||||
DeletePinFile();
|
||||
}
|
||||
|
||||
#region UserUpdated Event
|
||||
@@ -145,6 +150,16 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
return GetUserById(new Guid(id));
|
||||
}
|
||||
|
||||
public User GetUserByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
Users = await LoadUsers().ConfigureAwait(false);
|
||||
@@ -599,5 +614,157 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
|
||||
EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
|
||||
}
|
||||
|
||||
private string PasswordResetFile
|
||||
{
|
||||
get { return Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); }
|
||||
}
|
||||
|
||||
private string _lastPin;
|
||||
private PasswordPinCreationResult _lastPasswordPinCreationResult;
|
||||
private int _pinAttempts;
|
||||
|
||||
private 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 info = _appHost.GetSystemInfo();
|
||||
var localAddress = info.LocalAddress ?? string.Empty;
|
||||
|
||||
text.AppendLine("Use your web browser to visit:");
|
||||
text.AppendLine(string.Empty);
|
||||
text.AppendLine(localAddress + "/mediabrowser/web/forgotpasswordpin.html");
|
||||
text.AppendLine(string.Empty);
|
||||
text.AppendLine("Enter the following pin code:");
|
||||
text.AppendLine(string.Empty);
|
||||
text.AppendLine(pin);
|
||||
text.AppendLine(string.Empty);
|
||||
text.AppendLine("The pin code will expire at " + expiration.ToLocalTime().ToShortDateString() + " " + expiration.ToLocalTime().ToShortTimeString());
|
||||
|
||||
File.WriteAllText(path, text.ToString(), Encoding.UTF8);
|
||||
|
||||
var result = new PasswordPinCreationResult
|
||||
{
|
||||
PinFile = path,
|
||||
ExpirationDate = expiration
|
||||
};
|
||||
|
||||
_lastPasswordPinCreationResult = result;
|
||||
_pinAttempts = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ForgotPasswordResult StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
|
||||
{
|
||||
DeletePinFile();
|
||||
|
||||
var user = string.IsNullOrWhiteSpace(enteredUsername) ?
|
||||
null :
|
||||
GetUserByName(enteredUsername);
|
||||
|
||||
if (user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest)
|
||||
{
|
||||
throw new ArgumentException("Unable to process forgot password request for guests.");
|
||||
}
|
||||
|
||||
var action = ForgotPasswordAction.InNetworkRequired;
|
||||
string pinFile = null;
|
||||
DateTime? expirationDate = null;
|
||||
|
||||
if (user != null && !user.Configuration.IsAdministrator)
|
||||
{
|
||||
action = ForgotPasswordAction.ContactAdmin;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isInNetwork)
|
||||
{
|
||||
action = ForgotPasswordAction.PinCode;
|
||||
}
|
||||
|
||||
var result = CreatePasswordResetPin();
|
||||
pinFile = result.PinFile;
|
||||
expirationDate = result.ExpirationDate;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
var users = Users.Where(i => !i.ConnectLinkType.HasValue || i.ConnectLinkType.Value != UserLinkType.Guest)
|
||||
.ToList();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
await ResetPassword(user).ConfigureAwait(false);
|
||||
usersReset.Add(user.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pinAttempts++;
|
||||
if (_pinAttempts >= 3)
|
||||
{
|
||||
_lastPin = null;
|
||||
_lastPasswordPinCreationResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
return new PinRedeemResult
|
||||
{
|
||||
Success = valid,
|
||||
UsersReset = usersReset.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private void DeletePinFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(PasswordResetFile);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordPinCreationResult
|
||||
{
|
||||
public string PinFile { get; set; }
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,5 +624,12 @@
|
||||
"MessageLoggedOutParentalControl": "Access is currently restricted. Please try again later.",
|
||||
"DefaultErrorMessage": "There was an error processing the request. Please try again later.",
|
||||
"ButtonAccept": "Accept",
|
||||
"ButtonReject": "Reject"
|
||||
"ButtonReject": "Reject",
|
||||
"HeaderForgotPassword": "Forgot Password",
|
||||
"MessageContactAdminToResetPassword": "Please contact your system administrator to reset your password.",
|
||||
"MessageForgotPasswordInNetworkRequired": "Please try again within your home network to initiate the password reset process.",
|
||||
"MessageForgotPasswordFileCreated": "The following file has been created on your server and contains instructions on how to proceed:",
|
||||
"MessageForgotPasswordFileExpiration": "The reset pin will expire at {0}.",
|
||||
"MessageInvalidForgotPasswordPin": "An invalid or expired pin was entered. Please try again.",
|
||||
"MessagePasswordResetForUsers": "Passwords have been reset for the following users:"
|
||||
}
|
||||
|
||||
@@ -1270,5 +1270,11 @@
|
||||
"LabelSelectLastestItemsFolders": "Include media from the following sections in Latest Items",
|
||||
"HeaderShareMediaFolders": "Share Media Folders",
|
||||
"MessageGuestSharingPermissionsHelp": "Most features are initially unavailable to guests but can be enabled as needed.",
|
||||
"HeaderInvitations": "Invitations"
|
||||
"HeaderInvitations": "Invitations",
|
||||
"LabelForgotPasswordUsernameHelp": "Enter your username, if you remember it.",
|
||||
"HeaderForgotPassword": "Forgot Password",
|
||||
"TitleForgotPassword": "Forgot Password",
|
||||
"TitlePasswordReset": "Password Reset",
|
||||
"LabelPasswordRecoveryPinCode": "Pin code:",
|
||||
"HeaderPasswordReset": "Password Reset"
|
||||
}
|
||||
|
||||
@@ -1300,23 +1300,16 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||
/// Authenticates the new session.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="isLocal">if set to <c>true</c> [is local].</param>
|
||||
/// <returns>Task{SessionInfo}.</returns>
|
||||
/// <exception cref="AuthenticationException">Invalid user or password entered.</exception>
|
||||
/// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception>
|
||||
/// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</exception>
|
||||
public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request,
|
||||
bool isLocal)
|
||||
public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
|
||||
{
|
||||
var user = _userManager.Users
|
||||
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var allowWithoutPassword = isLocal &&
|
||||
string.Equals(request.App, "Dashboard", StringComparison.OrdinalIgnoreCase)
|
||||
&& !(user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest);
|
||||
|
||||
var result = allowWithoutPassword ||
|
||||
await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
|
||||
var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user