diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs index 718d5f402c..cdd348bb52 100644 --- a/MediaBrowser.Api/Images/ImageRequest.cs +++ b/MediaBrowser.Api/Images/ImageRequest.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using ServiceStack; namespace MediaBrowser.Api.Images @@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Images public bool EnableImageEnhancers { get; set; } [ApiMember(Name = "Format", Description = "Determines the output foramt of the image - original,gif,jpg,png", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public ImageOutputFormat Format { get; set; } + public string Format { get; set; } [ApiMember(Name = "AddPlayedIndicator", Description = "Optional. Add a played indicator", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool AddPlayedIndicator { get; set; } @@ -71,8 +70,6 @@ namespace MediaBrowser.Api.Images public ImageRequest() { EnableImageEnhancers = true; - - Format = ImageOutputFormat.Original; } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ca54249b31..7fc43e1642 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -542,7 +542,8 @@ namespace MediaBrowser.Api.Images }).ToList() : new List(); - var contentType = GetMimeType(request.Format, imageInfo.Path); + var format = GetOutputFormat(request, imageInfo, supportedImageEnhancers); + var contentType = GetMimeType(format, imageInfo.Path); var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers)); @@ -562,6 +563,7 @@ namespace MediaBrowser.Api.Images return GetImageResult(item, request, imageInfo, + format, supportedImageEnhancers, contentType, cacheDuration, @@ -573,6 +575,7 @@ namespace MediaBrowser.Api.Images private async Task GetImageResult(IHasImages item, ImageRequest request, ItemImageInfo image, + ImageOutputFormat format, List enhancers, string contentType, TimeSpan? cacheDuration, @@ -598,11 +601,11 @@ namespace MediaBrowser.Api.Images MaxWidth = request.MaxWidth, Quality = request.Quality, Width = request.Width, - OutputFormat = request.Format, AddPlayedIndicator = request.AddPlayedIndicator, PercentPlayed = request.PercentPlayed ?? 0, UnplayedCount = request.UnplayedCount, - BackgroundColor = request.BackgroundColor + BackgroundColor = request.BackgroundColor, + OutputFormat = format }; var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); @@ -617,6 +620,52 @@ namespace MediaBrowser.Api.Images }); } + private ImageOutputFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, List enhancers) + { + if (!string.IsNullOrWhiteSpace(request.Format)) + { + ImageOutputFormat format; + if (Enum.TryParse(request.Format, true, out format)) + { + return format; + } + } + + var serverFormats = _imageProcessor.GetSupportedImageOutputFormats(); + + var clientFormats = GetClientSupportedFormats(); + + if (serverFormats.Contains(ImageOutputFormat.Webp) && + clientFormats.Contains(ImageOutputFormat.Webp)) + { + return ImageOutputFormat.Webp; + } + + if (enhancers.Count > 0) + { + return ImageOutputFormat.Png; + } + + if (string.Equals(Path.GetExtension(image.Path), ".jpg", StringComparison.OrdinalIgnoreCase) || + string.Equals(Path.GetExtension(image.Path), ".jpeg", StringComparison.OrdinalIgnoreCase)) + { + return ImageOutputFormat.Jpg; + } + + // We can't predict if there will be transparency or not, so play it safe + return ImageOutputFormat.Png; + } + + private ImageOutputFormat[] GetClientSupportedFormats() + { + if (Request.AcceptTypes.Contains("image/webp", StringComparer.OrdinalIgnoreCase)) + { + return new[] { ImageOutputFormat.Webp, ImageOutputFormat.Jpg, ImageOutputFormat.Png }; + } + + return new[] { ImageOutputFormat.Jpg, ImageOutputFormat.Png }; + } + private string GetMimeType(ImageOutputFormat format, string path) { if (format == ImageOutputFormat.Bmp) diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs index 7135d9b84b..3c1f07c5f1 100644 --- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs @@ -598,6 +598,8 @@ namespace MediaBrowser.Api.Playback.Hls var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg; + args += " -r 24 -g 24"; + // Add resolution params, if specified if (!hasGraphicalSubs) { @@ -615,6 +617,9 @@ namespace MediaBrowser.Api.Playback.Hls protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) { + // test url http://192.168.1.2:8096/mediabrowser/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3 + // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/ + var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); @@ -624,7 +629,7 @@ namespace MediaBrowser.Api.Playback.Hls var segmentFilename = Path.GetFileNameWithoutExtension(outputPath) + "%03d" + GetSegmentFileExtension(state); - var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f ssegment -segment_time {6} -segment_format_options movflags=+faststart -segment_list_size {8} -segment_list \"{9}\" {10}", + var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}", inputModifier, GetInputArgument(transcodingJobId, state), threads, diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index aa01e1b65e..3b8eb7b6f4 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -44,9 +44,10 @@ namespace MediaBrowser.Api.System /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server. /// [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] - [Authenticated(AllowLocal = true)] public class ShutdownApplication { + // TODO: This is not currently authenticated due to uninstaller + // Improve later } [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 19bfb821ff..9bd85426d5 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -12,6 +12,7 @@ using ServiceStack; using ServiceStack.Text.Controller; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; @@ -175,6 +176,20 @@ namespace MediaBrowser.Api public string Name { get; set; } } + [Route("/Users/ForgotPassword", "POST", Summary = "Initiates the forgot password process for a local user")] + public class ForgotPassword : IReturn + { + [ApiMember(Name = "EnteredUsername", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] + public string EnteredUsername { get; set; } + } + + [Route("/Users/ForgotPassword/Pin", "POST", Summary = "Redeems a forgot password pin")] + public class ForgotPasswordPin : IReturn + { + [ApiMember(Name = "Pin", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Pin { get; set; } + } + /// /// Class UsersService /// @@ -217,34 +232,15 @@ namespace MediaBrowser.Api }); } - var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); - var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); - - if (Request.IsLocal && isDashboard) - { - var users = _userManager.Users - .Where(i => !i.Configuration.IsDisabled && !(i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest)) - .ToList(); - - return ToOptimizedResult(users); - } - - // TODO: Uncomment this once all clients can handle an empty user list. - return Get(new GetUsers - { - IsHidden = false, - IsDisabled = false - }); - - //// TODO: Add or is authenticated + // TODO: Uncomment once clients can handle an empty user list (and below) //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp)) - //{ - // return Get(new GetUsers - // { - // IsHidden = false, - // IsDisabled = false - // }); - //} + { + return Get(new GetUsers + { + IsHidden = false, + IsDisabled = false + }); + } //// Return empty when external //return ToOptimizedResult(new List()); @@ -379,7 +375,7 @@ namespace MediaBrowser.Api RemoteEndPoint = Request.RemoteIp, Username = request.Username - }, Request.IsLocal).ConfigureAwait(false); + }).ConfigureAwait(false); return ToOptimizedResult(result); } @@ -419,7 +415,7 @@ namespace MediaBrowser.Api await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false); } } - + /// /// Posts the specified request. /// @@ -510,5 +506,22 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + + /// + /// Posts the specified request. + /// + /// The request. + /// System.Object. + public object Post(ForgotPassword request) + { + var isLocal = Request.IsLocal || _networkManager.IsInLocalNetwork(Request.RemoteIp); + + return _userManager.StartForgotPasswordProcess(request.EnteredUsername, isLocal); + } + + public object Post(ForgotPasswordPin request) + { + return _userManager.RedeemPasswordResetPin(request.Pin); + } } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index a0128f1113..3bd3335276 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -89,5 +89,11 @@ namespace MediaBrowser.Controller.Drawing /// Index of the image. /// Task{System.String}. Task GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex); + + /// + /// Gets the supported image output formats. + /// + /// ImageOutputFormat[]. + ImageOutputFormat[] GetSupportedImageOutputFormats(); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index fbbf217975..b99186f37f 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; +using System; using System.Collections.Generic; using System.IO; @@ -59,12 +60,7 @@ namespace MediaBrowser.Controller.Drawing private bool IsOutputFormatDefault(string originalImagePath) { - if (OutputFormat == ImageOutputFormat.Original) - { - return true; - } - - return string.Equals(Path.GetExtension(originalImagePath), "." + OutputFormat); + return string.Equals(Path.GetExtension(originalImagePath), "." + OutputFormat, StringComparison.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6e084e9d81..bd44f786f9 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Library { @@ -56,6 +57,13 @@ namespace MediaBrowser.Controller.Library /// User. User GetUserById(string id); + /// + /// Gets the name of the user by. + /// + /// The name. + /// User. + User GetUserByName(string name); + /// /// Authenticates a User and returns a result indicating whether or not it succeeded /// @@ -141,5 +149,20 @@ namespace MediaBrowser.Controller.Library /// The remote end point. /// Task<System.Boolean>. Task AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint); + + /// + /// Starts the forgot password process. + /// + /// The entered username. + /// if set to true [is in network]. + /// ForgotPasswordResult. + ForgotPasswordResult StartForgotPasswordProcess(string enteredUsername, bool isInNetwork); + + /// + /// Redeems the password reset pin. + /// + /// The pin. + /// true if XXXX, false otherwise. + Task RedeemPasswordResetPin(string pin); } } diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index 83e404dfce..17c91c9773 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -9,12 +9,6 @@ namespace MediaBrowser.Controller.Net { public IAuthService AuthService { get; set; } - /// - /// Gets or sets a value indicating whether or not to allow local unauthenticated access. - /// - /// true if [allow local]; otherwise, false. - public bool AllowLocal { get; set; } - /// /// Gets or sets the roles. /// @@ -70,7 +64,6 @@ namespace MediaBrowser.Controller.Net { bool EscapeParentalControl { get; } - bool AllowLocal { get; } IEnumerable GetRoles(); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index c048e3fab7..8bc5168875 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -223,9 +223,8 @@ namespace MediaBrowser.Controller.Session /// Authenticates the new session. /// /// The request. - /// if set to true [is local]. /// Task{SessionInfo}. - Task AuthenticateNewSession(AuthenticationRequest request, bool isLocal); + Task AuthenticateNewSession(AuthenticationRequest request); /// /// Reports the capabilities. diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index 5d9bd0c7d2..879f3045f6 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,4 @@ -using System.Linq; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -16,6 +15,7 @@ using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index f67094554f..c43efcd1da 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -124,7 +124,6 @@ namespace MediaBrowser.Dlna.PlayTo { _logger.Debug("RestartTimer"); _timer.Change(10, GetPlaybackTimerIntervalMs()); - _volumeTimer.Change(100, GetVolumeTimerIntervalMs()); } @@ -147,8 +146,14 @@ namespace MediaBrowser.Dlna.PlayTo _logger.Debug("RestartTimerInactive"); var interval = GetInactiveTimerIntervalMs(); - _timer.Change(interval, interval); - _volumeTimer.Change(Timeout.Infinite, Timeout.Infinite); + if (_timer != null) + { + _timer.Change(interval, interval); + } + if (_volumeTimer != null) + { + _volumeTimer.Change(Timeout.Infinite, Timeout.Infinite); + } } _timerActive = false; diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 821ac7b7f6..1daf0e4ac4 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -74,8 +74,6 @@ - - diff --git a/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs deleted file mode 100644 index ba334da8c1..0000000000 --- a/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs +++ /dev/null @@ -1,76 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; - -namespace MediaBrowser.LocalMetadata.Savers -{ - class AlbumXmlSaver : IMetadataFileSaver - { - public string Name - { - get - { - return "Media Browser Xml"; - } - } - - private readonly IServerConfigurationManager _config; - - public AlbumXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - - /// - /// Determines whether [is enabled for] [the specified item]. - /// - /// The item. - /// Type of the update. - /// true if [is enabled for] [the specified item]; otherwise, false. - public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) - { - if (!item.SupportsLocalMetadata) - { - return false; - } - - return item is MusicAlbum && updateType >= ItemUpdateType.MetadataDownload; - } - - /// - /// Saves the specified item. - /// - /// The item. - /// The cancellation token. - /// Task. - public void Save(IHasMetadata item, CancellationToken cancellationToken) - { - var builder = new StringBuilder(); - - builder.Append(""); - - XmlSaverHelpers.AddCommonNodes((MusicAlbum)item, builder); - - builder.Append(""); - - var xmlFilePath = GetSavePath(item); - - XmlSaverHelpers.Save(builder, xmlFilePath, new List { }, _config); - } - - /// - /// Gets the save path. - /// - /// The item. - /// System.String. - public string GetSavePath(IHasMetadata item) - { - return Path.Combine(item.Path, "album.xml"); - } - } -} diff --git a/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs deleted file mode 100644 index 5b74db2328..0000000000 --- a/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs +++ /dev/null @@ -1,76 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; - -namespace MediaBrowser.LocalMetadata.Savers -{ - class ArtistXmlSaver : IMetadataFileSaver - { - public string Name - { - get - { - return "Media Browser Xml"; - } - } - - private readonly IServerConfigurationManager _config; - - public ArtistXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - - /// - /// Determines whether [is enabled for] [the specified item]. - /// - /// The item. - /// Type of the update. - /// true if [is enabled for] [the specified item]; otherwise, false. - public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) - { - if (!item.SupportsLocalMetadata) - { - return false; - } - - return item is MusicArtist && updateType >= ItemUpdateType.MetadataDownload; - } - - /// - /// Saves the specified item. - /// - /// The item. - /// The cancellation token. - /// Task. - public void Save(IHasMetadata item, CancellationToken cancellationToken) - { - var builder = new StringBuilder(); - - builder.Append(""); - - XmlSaverHelpers.AddCommonNodes((MusicArtist)item, builder); - - builder.Append(""); - - var xmlFilePath = GetSavePath(item); - - XmlSaverHelpers.Save(builder, xmlFilePath, new List { }, _config); - } - - /// - /// Gets the save path. - /// - /// The item. - /// System.String. - public string GetSavePath(IHasMetadata item) - { - return Path.Combine(item.Path, "artist.xml"); - } - } -} diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index c1f8ef7d56..b73dae27bd 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -1103,6 +1103,15 @@ Users\AuthenticationResult.cs + + Users\ForgotPasswordAction.cs + + + Users\ForgotPasswordResult.cs + + + Users\PinRedeemResult.cs + Properties\SharedVersion.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 731af59d38..68c65de1c1 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -1062,6 +1062,15 @@ Users\AuthenticationResult.cs + + Users\ForgotPasswordAction.cs + + + Users\ForgotPasswordResult.cs + + + Users\PinRedeemResult.cs + Properties\SharedVersion.cs diff --git a/MediaBrowser.Model/Drawing/ImageOutputFormat.cs b/MediaBrowser.Model/Drawing/ImageOutputFormat.cs index 824970073a..ec32f64f26 100644 --- a/MediaBrowser.Model/Drawing/ImageOutputFormat.cs +++ b/MediaBrowser.Model/Drawing/ImageOutputFormat.cs @@ -6,10 +6,6 @@ namespace MediaBrowser.Model.Drawing /// public enum ImageOutputFormat { - /// - /// The original - /// - Original, /// /// The BMP /// @@ -26,6 +22,9 @@ namespace MediaBrowser.Model.Drawing /// The PNG /// Png, + /// + /// The webp + /// Webp } } diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs index 08ac7906a7..037be4a877 100644 --- a/MediaBrowser.Model/Dto/ImageOptions.cs +++ b/MediaBrowser.Model/Dto/ImageOptions.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; -using System; namespace MediaBrowser.Model.Dto { @@ -74,7 +73,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the format. /// /// The format. - public ImageOutputFormat Format { get; set; } + public ImageOutputFormat? Format { get; set; } /// /// Gets or sets a value indicating whether [add played indicator]. @@ -100,8 +99,6 @@ namespace MediaBrowser.Model.Dto public ImageOptions() { EnableImageEnhancers = true; - - Format = ImageOutputFormat.Original; } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 8f84edb258..3a6e8d3e5a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -407,6 +407,9 @@ + + + diff --git a/MediaBrowser.Model/Users/ForgotPasswordAction.cs b/MediaBrowser.Model/Users/ForgotPasswordAction.cs new file mode 100644 index 0000000000..f75b1d74bf --- /dev/null +++ b/MediaBrowser.Model/Users/ForgotPasswordAction.cs @@ -0,0 +1,10 @@ + +namespace MediaBrowser.Model.Users +{ + public enum ForgotPasswordAction + { + ContactAdmin = 0, + PinCode = 1, + InNetworkRequired = 2 + } +} diff --git a/MediaBrowser.Model/Users/ForgotPasswordResult.cs b/MediaBrowser.Model/Users/ForgotPasswordResult.cs new file mode 100644 index 0000000000..7dbb1e96bd --- /dev/null +++ b/MediaBrowser.Model/Users/ForgotPasswordResult.cs @@ -0,0 +1,23 @@ +using System; + +namespace MediaBrowser.Model.Users +{ + public class ForgotPasswordResult + { + /// + /// Gets or sets the action. + /// + /// The action. + public ForgotPasswordAction Action { get; set; } + /// + /// Gets or sets the pin file. + /// + /// The pin file. + public string PinFile { get; set; } + /// + /// Gets or sets the pin expiration date. + /// + /// The pin expiration date. + public DateTime? PinExpirationDate { get; set; } + } +} diff --git a/MediaBrowser.Model/Users/PinRedeemResult.cs b/MediaBrowser.Model/Users/PinRedeemResult.cs new file mode 100644 index 0000000000..6a01bf2d42 --- /dev/null +++ b/MediaBrowser.Model/Users/PinRedeemResult.cs @@ -0,0 +1,17 @@ + +namespace MediaBrowser.Model.Users +{ + public class PinRedeemResult + { + /// + /// Gets or sets a value indicating whether this is success. + /// + /// true if success; otherwise, false. + public bool Success { get; set; } + /// + /// Gets or sets the users reset. + /// + /// The users reset. + public string[] UsersReset { get; set; } + } +} diff --git a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs index 722d0d3f3e..289c5661fb 100644 --- a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs @@ -230,7 +230,7 @@ namespace MediaBrowser.Providers.Subtitles } Utilities.HttpClient = _httpClient; - OpenSubtitles.SetUserAgent("OS Test User Agent"); + OpenSubtitles.SetUserAgent("mediabrowser.tv"); await Login(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index f2ab64a52a..f0d3b76e5a 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -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) diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index c8e923b125..feda361c82 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -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 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; diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 295f0c775a..dde61079b9 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -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); } } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 55311ed8d4..9e3fd3f176 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -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 _imageProcessorFactory; private readonly Func _dtoServiceFactory; private readonly Func _connectFactory; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; /// /// Initializes a new instance of the class. @@ -73,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.Library /// The logger. /// The configuration manager. /// The user repository. - public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func imageProcessorFactory, Func dtoServiceFactory, Func connectFactory, IApplicationHost appHost) + public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func imageProcessorFactory, Func dtoServiceFactory, Func connectFactory, IServerApplicationHost appHost) { _logger = logger; UserRepository = userRepository; @@ -85,6 +88,8 @@ namespace MediaBrowser.Server.Implementations.Library _appHost = appHost; ConfigurationManager = configurationManager; Users = new List(); + + 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 { 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 RedeemPasswordResetPin(string pin) + { + DeletePinFile(); + + var usersReset = new List(); + + 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; } + } + } } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 5920ab6f02..a484cbbb87 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -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:" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 099f3f75b7..307490a190 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -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" } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ac80687441..d6db836cc7 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1300,23 +1300,16 @@ namespace MediaBrowser.Server.Implementations.Session /// Authenticates the new session. /// /// The request. - /// if set to true [is local]. /// Task{SessionInfo}. /// Invalid user or password entered. /// Invalid user or password entered. /// Invalid user or password entered. - public async Task AuthenticateNewSession(AuthenticationRequest request, - bool isLocal) + public async Task 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) { diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index e6cea54632..e80284356f 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -36,7 +36,9 @@ namespace MediaBrowser.ServerApplication var options = new StartupOptions(); _isRunningAsService = options.ContainsOption("-service"); - var applicationPath = Process.GetCurrentProcess().MainModule.FileName; + var currentProcess = Process.GetCurrentProcess(); + + var applicationPath = currentProcess.MainModule.FileName; var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService); @@ -84,8 +86,6 @@ namespace MediaBrowser.ServerApplication RunServiceInstallationIfNeeded(applicationPath); - var currentProcess = Process.GetCurrentProcess(); - if (IsAlreadyRunning(applicationPath, currentProcess)) { logger.Info("Shutting down because another instance of Media Browser Server is already running."); diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 68de9ac496..18f2e54a3c 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -374,6 +374,8 @@ namespace MediaBrowser.WebDashboard.Api "externalplayer.js", "favorites.js", + "forgotpassword.js", + "forgotpasswordpin.js", "gamesrecommendedpage.js", "gamesystemspage.js", "gamespage.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 9a9552d5da..3dded5d98f 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -90,6 +90,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -2232,6 +2241,9 @@ PreserveNewest + + PreserveNewest +