diff --git a/MediaBrowser.Api/AppThemeService.cs b/MediaBrowser.Api/AppThemeService.cs index 4d8eed7ddb..0c8a0aaa63 100644 --- a/MediaBrowser.Api/AppThemeService.cs +++ b/MediaBrowser.Api/AppThemeService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Themes; using MediaBrowser.Model.Themes; using ServiceStack; @@ -47,6 +48,7 @@ namespace MediaBrowser.Api { } + [Authenticated] public class AppThemeService : BaseApiService { private readonly IAppThemeManager _themeManager; diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs deleted file mode 100644 index 6c56083cbf..0000000000 --- a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs +++ /dev/null @@ -1,190 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using ServiceStack.Web; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Api -{ - public class AuthorizationRequestFilterAttribute : Attribute, IHasRequestFilter - { - //This property will be resolved by the IoC container - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } - - public ISessionManager SessionManager { get; set; } - - /// - /// Gets or sets the logger. - /// - /// The logger. - public ILogger Logger { get; set; } - - /// - /// The request filter is executed before the service. - /// - /// The http request wrapper - /// The http response wrapper - /// The request DTO - public void RequestFilter(IRequest request, IResponse response, object requestDto) - { - //This code is executed before the service - var auth = GetAuthorizationDictionary(request); - - if (auth != null) - { - User user = null; - - if (auth.ContainsKey("UserId")) - { - var userId = auth["UserId"]; - - if (!string.IsNullOrEmpty(userId)) - { - user = UserManager.GetUserById(new Guid(userId)); - } - } - - string deviceId; - string device; - string client; - string version; - - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - - if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) - { - var remoteEndPoint = request.RemoteIp; - - SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); - } - } - } - - /// - /// Gets the auth. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorizationDictionary(IRequest httpReq) - { - var auth = httpReq.Headers["Authorization"]; - - return GetAuthorization(auth); - } - - public static User GetCurrentUser(IRequest httpReq, IUserManager userManager) - { - var info = GetAuthorization(httpReq); - - return string.IsNullOrEmpty(info.UserId) ? null : - userManager.GetUserById(new Guid(info.UserId)); - } - - /// - /// Gets the authorization. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - public static AuthorizationInfo GetAuthorization(IRequest httpReq) - { - var auth = GetAuthorizationDictionary(httpReq); - - string userId = null; - string deviceId = null; - string device = null; - string client = null; - string version = null; - - if (auth != null) - { - auth.TryGetValue("UserId", out userId); - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - } - - return new AuthorizationInfo - { - Client = client, - Device = device, - DeviceId = deviceId, - UserId = userId, - Version = version - }; - } - - /// - /// Gets the authorization. - /// - /// The authorization header. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorization(string authorizationHeader) - { - if (authorizationHeader == null) return null; - - var parts = authorizationHeader.Split(' '); - - // There should be at least to parts - if (parts.Length < 2) return null; - - // It has to be a digest request - if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Remove uptil the first space - authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); - parts = authorizationHeader.Split(','); - - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split(new[] { '=' }, 2); - result.Add(param[0], param[1].Trim(new[] { '"' })); - } - - return result; - } - - /// - /// A new shallow copy of this filter is used on every request. - /// - /// IHasRequestFilter. - public IHasRequestFilter Copy() - { - return this; - } - - /// - /// Order in which Request Filters are executed. - /// <0 Executed before global request filters - /// >0 Executed after global request filters - /// - /// The priority. - public int Priority - { - get { return 0; } - } - } - - public class AuthorizationInfo - { - public string UserId; - public string DeviceId; - public string Device; - public string Client; - public string Version; - } -} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index f1d5962139..09eb1ea414 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -14,8 +14,7 @@ namespace MediaBrowser.Api /// /// Class BaseApiService /// - [AuthorizationRequestFilter] - public class BaseApiService : IHasResultFactory, IRestfulService + public class BaseApiService : IHasResultFactory, IRestfulService, IHasSession { /// /// Gets or sets the logger. @@ -35,6 +34,8 @@ namespace MediaBrowser.Api /// The request context. public IRequest Request { get; set; } + public ISessionContext SessionContext { get; set; } + public string GetHeader(string name) { return Request.Headers[name]; @@ -82,13 +83,11 @@ namespace MediaBrowser.Api /// /// Gets the session. /// - /// The session manager. /// SessionInfo. - protected SessionInfo GetSession(ISessionManager sessionManager) + /// Session not found. + protected SessionInfo GetSession() { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - var session = sessionManager.GetSession(auth.DeviceId, auth.Client, auth.Version); + var session = SessionContext.GetSession(Request); if (session == null) { diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index d71db929fe..2cc046f1d4 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -172,7 +173,8 @@ namespace MediaBrowser.Api [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } } - + + [Authenticated] public class ChannelService : BaseApiService { private readonly IChannelManager _channelManager; diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 206f5bf7b7..9f3a6134e9 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using ServiceStack; @@ -48,6 +49,7 @@ namespace MediaBrowser.Api /// /// Class DisplayPreferencesService /// + [Authenticated] public class DisplayPreferencesService : BaseApiService { /// diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 56f71fc00e..590deff5a7 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using ServiceStack; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api /// /// Class EnvironmentService /// + [Authenticated] public class EnvironmentService : BaseApiService { const char UncSeparator = '\\'; diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index ff2771ce1b..9aba2b0652 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using ServiceStack; @@ -51,6 +52,7 @@ namespace MediaBrowser.Api /// /// Class GamesService /// + [Authenticated] public class GamesService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index ddb2dc943e..77a714755d 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -470,7 +470,7 @@ namespace MediaBrowser.Api.Library { var item = _libraryManager.GetItemById(request.Id); - var session = GetSession(_sessionManager); + var session = GetSession(); if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion) { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 6973a233af..de01628f84 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -280,7 +280,7 @@ namespace MediaBrowser.Api.LiveTv private void AssertUserCanManageLiveTv() { - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, _userManager); + var user = SessionContext.GetUser(Request); if (user == null) { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 3f1d9fe67d..a68966b335 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -79,7 +79,6 @@ - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 380ece2f2d..9ff482a1ab 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1386,8 +1386,6 @@ namespace MediaBrowser.Api.Playback ParseParams(request); } - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager); - var url = Request.PathInfo; if (string.IsNullOrEmpty(request.AudioCodec)) @@ -1409,11 +1407,6 @@ namespace MediaBrowser.Api.Playback var item = LibraryManager.GetItemById(request.Id); - if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) - { - throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name)); - } - List mediaStreams = null; state.ItemType = item.GetType().Name; diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 36f1d65778..00c307a18b 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.Api SeekPositionTicks = request.SeekPositionTicks }; - var task = _sessionManager.SendPlaystateCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -303,7 +303,7 @@ namespace MediaBrowser.Api ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -318,7 +318,7 @@ namespace MediaBrowser.Api if (Enum.TryParse(request.Command, true, out commandType)) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -345,7 +345,7 @@ namespace MediaBrowser.Api Text = request.Text }; - var task = _sessionManager.SendMessageCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -364,14 +364,14 @@ namespace MediaBrowser.Api StartPositionTicks = request.StartPositionTicks }; - var task = _sessionManager.SendPlayCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } public void Post(SendGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -386,7 +386,7 @@ namespace MediaBrowser.Api public void Post(SendFullGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; @@ -409,7 +409,7 @@ namespace MediaBrowser.Api { if (string.IsNullOrWhiteSpace(request.Id)) { - request.Id = GetSession(_sessionManager).Id; + request.Id = GetSession().Id; } _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities { diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index a1625d0520..da12a9e3d2 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.UserLibrary datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); @@ -826,7 +826,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStart request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStart(request); @@ -854,7 +854,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackProgress request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackProgress(request); @@ -877,7 +877,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStopped request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStopped(request); @@ -899,7 +899,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 1932c8f93a..764a281024 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; @@ -152,7 +153,7 @@ namespace MediaBrowser.Api /// /// Class UsersService /// - public class UserService : BaseApiService + public class UserService : BaseApiService, IHasAuthorization { /// /// The _XML serializer @@ -166,6 +167,8 @@ namespace MediaBrowser.Api private readonly IDtoService _dtoService; private readonly ISessionManager _sessionMananger; + public IAuthorizationContext AuthorizationContext { get; set; } + /// /// Initializes a new instance of the class. /// @@ -295,7 +298,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + var auth = AuthorizationContext.GetAuthorizationInfo(Request); // Login in the old way if the header is missing if (string.IsNullOrEmpty(auth.Client) || diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 52c414ae8e..584091b130 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -797,9 +797,8 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// The list. /// if set to true [recursive]. - /// The filter. /// true if XXXX, false otherwise - private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive, Func filter) + private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive) { var hasLinkedChildren = false; @@ -807,19 +806,16 @@ namespace MediaBrowser.Controller.Entities { if (child.IsVisible(user)) { - if (filter == null || filter(child)) + if (!child.IsHiddenFromUser(user)) { - if (!child.IsHiddenFromUser(user)) - { - list.Add(child); - } + list.Add(child); } if (recursive && child.IsFolder) { var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true)) { hasLinkedChildren = true; } @@ -831,11 +827,6 @@ namespace MediaBrowser.Controller.Entities { foreach (var child in GetLinkedChildren()) { - if (filter != null && !filter(child)) - { - continue; - } - if (child.IsVisible(user)) { hasLinkedChildren = true; @@ -864,7 +855,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1310e77973..ae7118be94 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -195,10 +195,18 @@ + + + + + + + + diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs new file mode 100644 index 0000000000..567d20f39c --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -0,0 +1,41 @@ +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class AuthenticatedAttribute : Attribute, IHasRequestFilter + { + public IAuthService AuthService { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + AuthService.Authenticate(request, response, requestDto); + } + + /// + /// A new shallow copy of this filter is used on every request. + /// + /// IHasRequestFilter. + public IHasRequestFilter Copy() + { + return this; + } + + /// + /// Order in which Request Filters are executed. + /// <0 Executed before global request filters + /// >0 Executed after global request filters + /// + /// The priority. + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs new file mode 100644 index 0000000000..e609204d69 --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -0,0 +1,32 @@ + +namespace MediaBrowser.Controller.Net +{ + public class AuthorizationInfo + { + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + /// + /// Gets or sets the device. + /// + /// The device. + public string Device { get; set; } + /// + /// Gets or sets the client. + /// + /// The client. + public string Client { get; set; } + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs new file mode 100644 index 0000000000..41859395b6 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -0,0 +1,9 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthService + { + void Authenticate(IRequest request, IResponse response, object requestDto); + } +} diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs new file mode 100644 index 0000000000..9cf5623700 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -0,0 +1,14 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthorizationContext + { + /// + /// Gets the authorization information. + /// + /// The request context. + /// AuthorizationInfo. + AuthorizationInfo GetAuthorizationInfo(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/IHasAuthorization.cs b/MediaBrowser.Controller/Net/IHasAuthorization.cs new file mode 100644 index 0000000000..6fc70159dd --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasAuthorization.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasAuthorization + { + /// + /// Gets or sets the authorization context. + /// + /// The authorization context. + IAuthorizationContext AuthorizationContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IHasResultFactory.cs b/MediaBrowser.Controller/Net/IHasResultFactory.cs index a87113d1f4..3988b8d615 100644 --- a/MediaBrowser.Controller/Net/IHasResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHasResultFactory.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using ServiceStack.Web; +using ServiceStack.Web; namespace MediaBrowser.Controller.Net { diff --git a/MediaBrowser.Controller/Net/IHasSession.cs b/MediaBrowser.Controller/Net/IHasSession.cs new file mode 100644 index 0000000000..e762c1e844 --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasSession.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasSession + { + /// + /// Gets or sets the session context. + /// + /// The session context. + ISessionContext SessionContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IRestfulService.cs b/MediaBrowser.Controller/Net/IRestfulService.cs index f55012b734..7d07bb0941 100644 --- a/MediaBrowser.Controller/Net/IRestfulService.cs +++ b/MediaBrowser.Controller/Net/IRestfulService.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Net /// /// Interface IRestfulService /// + [Logged] public interface IRestfulService : IService { } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs new file mode 100644 index 0000000000..31ccd53731 --- /dev/null +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface ISessionContext + { + SessionInfo GetSession(IRequest requestContext); + + User GetUser(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/LoggedAttribute.cs b/MediaBrowser.Controller/Net/LoggedAttribute.cs new file mode 100644 index 0000000000..6df72f7a7e --- /dev/null +++ b/MediaBrowser.Controller/Net/LoggedAttribute.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class LoggedAttribute : Attribute, IHasRequestFilter + { + public ILogger Logger { get; set; } + public IUserManager UserManager { get; set; } + public ISessionManager SessionManager { get; set; } + public IAuthorizationContext AuthorizationContext { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + //This code is executed before the service + var auth = AuthorizationContext.GetAuthorizationInfo(request); + + if (auth != null) + { + User user = null; + + if (!string.IsNullOrWhiteSpace(auth.UserId)) + { + var userId = auth.UserId; + + user = UserManager.GetUserById(new Guid(userId)); + } + + string deviceId = auth.DeviceId; + string device = auth.Device; + string client = auth.Client; + string version = auth.Version; + + if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + { + var remoteEndPoint = request.RemoteIp; + + SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + } + } + } + + /// + /// A new shallow copy of this filter is used on every request. + /// + /// IHasRequestFilter. + public IHasRequestFilter Copy() + { + return this; + } + + /// + /// Order in which Request Filters are executed. + /// <0 Executed before global request filters + /// >0 Executed after global request filters + /// + /// The priority. + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 717b935243..95eca6ba0c 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -340,13 +340,17 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { + Logger.ErrorException("Error in {0}", ex, provider.Name); + // If a local provider fails, consider that a failure refreshResult.Status = ProviderRefreshStatus.Failure; refreshResult.ErrorMessage = ex.Message; - Logger.ErrorException("Error in {0}", ex, provider.Name); - // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost - return refreshResult; + if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh) + { + // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost + return refreshResult; + } } } diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index 739b42192b..07c86ed314 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -12,12 +12,8 @@ namespace MediaBrowser.Server.Implementations.Collections public override bool IsVisible(User user) { - if (!GetChildren(user, true).Any()) - { - return false; - } - - return base.IsVisible(user); + return GetChildren(user, true).Any() && + base.IsVisible(user); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 0fc9265f69..833dfc5e45 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,13 +1,13 @@ -using System.Net.Sockets; -using System.Runtime.Serialization; -using Funq; +using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.HttpServer.Security; using ServiceStack; using ServiceStack.Api.Swagger; +using ServiceStack.Auth; using ServiceStack.Host; using ServiceStack.Host.Handlers; using ServiceStack.Host.HttpListener; @@ -27,7 +27,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { public class HttpListenerHost : ServiceStackHost, IHttpServer { - private string ServerName { get; set; } private string HandlerPath { get; set; } private string DefaultRedirectPath { get; set; } @@ -59,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer : base(serviceName, assembliesWithServices) { DefaultRedirectPath = defaultRedirectPath; - ServerName = serviceName; HandlerPath = handlerPath; _logger = logManager.GetLogger("HttpServer"); @@ -95,7 +93,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer container.Adapter = _containerAdapter; Plugins.Add(new SwaggerFeature()); - Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + + Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { + new SessionAuthProvider(_containerAdapter.Resolve()), + })); + HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } @@ -112,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Config.HandlerFactoryPath = string.IsNullOrEmpty(HandlerPath) ? null - : HandlerPath; + : "/" + HandlerPath; Config.MetadataRedirectPath = string.IsNullOrEmpty(HandlerPath) ? "metadata" @@ -161,8 +164,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (Listener == null) Listener = new HttpListener(); - HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - foreach (var prefix in UrlPrefixes) { _logger.Info("Adding HttpListener prefix " + prefix); @@ -172,6 +173,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer IsStarted = true; _logger.Info("Starting HttpListner"); Listener.Start(); + _logger.Info("HttpListener started"); for (var i = 0; i < _autoResetEvents.Count; i++) { @@ -263,27 +265,27 @@ namespace MediaBrowser.Server.Implementations.HttpServer var localPath = request.Url.LocalPath; - if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) { context.Response.Redirect(DefaultRedirectPath); context.Response.Close(); return; } - if (string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.IsNullOrEmpty(localPath)) { - context.Response.Redirect("/mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } @@ -410,6 +412,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); req.RequestAttributes = req.GetAttributes(); + return req; } @@ -442,7 +445,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; + //var pathInfo = httpReq.PathInfo; + var handler = HttpHandlerFactory.GetHandler(httpReq); + //var handler = HttpHandlerFactory.GetHandlerForPathInfo(httpReq.HttpMethod, pathInfo, pathInfo, httpReq.GetPhysicalPath()); var serviceStackHandler = handler as IServiceStackHandler; if (serviceStackHandler != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 1ff199eb4d..9de6972f86 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -228,5 +228,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } public string StatusDescription { get; set; } + + public int PaddingLength { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs new file mode 100644 index 0000000000..ddb583f5d8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -0,0 +1,113 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; +using ServiceStack.Web; +using System; +using System.Collections.Specialized; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthService : IAuthService + { + /// + /// Restrict authentication to a specific . + /// For example, if this attribute should only permit access + /// if the user is authenticated with , + /// you should set this property to . + /// + public string Provider { get; set; } + + /// + /// Redirect the client to a specific URL if authentication failed. + /// If this property is null, simply `401 Unauthorized` is returned. + /// + public string HtmlRedirect { get; set; } + + public void Authenticate(IRequest req, IResponse res, object requestDto) + { + if (HostContext.HasValidAuthSecret(req)) + return; + + ExecuteBasic(req, res, requestDto); //first check if session is authenticated + if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) + + ValidateUser(req); + } + + private void ValidateUser(IRequest req) + { + var user = req.TryResolve().GetUser(req); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("Unauthorized access."); + } + } + + private void ExecuteBasic(IRequest req, IResponse res, object requestDto) + { + if (AuthenticateService.AuthProviders == null) + throw new InvalidOperationException( + "The AuthService must be initialized by calling AuthService.Init to use an authenticate attribute"); + + var matchingOAuthConfigs = AuthenticateService.AuthProviders.Where(x => + this.Provider.IsNullOrEmpty() + || x.Provider == this.Provider).ToList(); + + if (matchingOAuthConfigs.Count == 0) + { + res.WriteError(req, requestDto, "No OAuth Configs found matching {0} provider" + .Fmt(this.Provider ?? "any")); + res.EndRequest(); + } + + matchingOAuthConfigs.OfType() + .Each(x => x.PreAuthenticate(req, res)); + + var session = req.GetSession(); + if (session == null || !matchingOAuthConfigs.Any(x => session.IsAuthorized(x.Provider))) + { + if (this.DoHtmlRedirectIfConfigured(req, res, true)) return; + + AuthProvider.HandleFailedAuth(matchingOAuthConfigs[0], session, req, res); + } + } + + protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) + { + var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; + if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html)) + { + DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam); + return true; + } + return false; + } + + public static void DoHtmlRedirect(string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam) + { + var url = req.ResolveAbsoluteUrl(redirectUrl); + if (includeRedirectParam) + { + var absoluteRequestPath = req.ResolveAbsoluteUrl("~" + req.PathInfo + ToQueryString(req.QueryString)); + url = url.AddQueryParam(HostContext.ResolveLocalizedString(LocalizedStrings.Redirect), absoluteRequestPath); + } + + res.RedirectToUrl(url); + } + + private static string ToQueryString(INameValueCollection queryStringCollection) + { + return ToQueryString((NameValueCollection)queryStringCollection.Original); + } + + private static string ToQueryString(NameValueCollection queryStringCollection) + { + if (queryStringCollection == null || queryStringCollection.Count == 0) + return String.Empty; + + return "?" + queryStringCollection.ToFormUrlEncoded(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs new file mode 100644 index 0000000000..6ea77f2518 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Net; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthorizationContext : IAuthorizationContext + { + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) + { + return GetAuthorization(requestContext); + } + + /// + /// Gets the authorization. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static AuthorizationInfo GetAuthorization(IRequest httpReq) + { + var auth = GetAuthorizationDictionary(httpReq); + + string userId = null; + string deviceId = null; + string device = null; + string client = null; + string version = null; + + if (auth != null) + { + auth.TryGetValue("UserId", out userId); + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out device); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + } + + return new AuthorizationInfo + { + Client = client, + Device = device, + DeviceId = deviceId, + UserId = userId, + Version = version + }; + } + + /// + /// Gets the auth. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorizationDictionary(IRequest httpReq) + { + var auth = httpReq.Headers["Authorization"]; + + return GetAuthorization(auth); + } + + /// + /// Gets the authorization. + /// + /// The authorization header. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorization(string authorizationHeader) + { + if (authorizationHeader == null) return null; + + var parts = authorizationHeader.Split(' '); + + // There should be at least to parts + if (parts.Length < 2) return null; + + // It has to be a digest request + if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Remove uptil the first space + authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); + parts = authorizationHeader.Split(','); + + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var item in parts) + { + var param = item.Trim().Split(new[] { '=' }, 2); + result.Add(param[0], param[1].Trim(new[] { '"' })); + } + + return result; + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs new file mode 100644 index 0000000000..7c31731012 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionAuthProvider : CredentialsAuthProvider + { + private readonly ISessionContext _sessionContext; + + public SessionAuthProvider(ISessionContext sessionContext) + { + _sessionContext = sessionContext; + } + + public override bool TryAuthenticate(IServiceBase authService, string userName, string password) + { + return true; + } + + public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null) + { + return true; + } + + protected override void SaveUserAuth(IServiceBase authService, IAuthSession session, IAuthRepository authRepo, IAuthTokens tokens) + { + } + + public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request) + { + return base.Authenticate(authService, session, request); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs new file mode 100644 index 0000000000..f67c643c82 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionContext : ISessionContext + { + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + private readonly IAuthorizationContext _authContext; + + public SessionContext(IUserManager userManager, IAuthorizationContext authContext, ISessionManager sessionManager) + { + _userManager = userManager; + _authContext = authContext; + _sessionManager = sessionManager; + } + + public SessionInfo GetSession(IRequest requestContext) + { + var authorization = _authContext.GetAuthorizationInfo(requestContext); + + return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version); + } + + public User GetUser(IRequest requestContext) + { + var session = GetSession(requestContext); + + return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 3401078518..10e27bc304 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -133,6 +133,7 @@ + @@ -141,9 +142,12 @@ + + + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 87f18e4cba..dbca940657 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -62,6 +62,7 @@ using MediaBrowser.Server.Implementations.Dto; using MediaBrowser.Server.Implementations.EntryPoints; using MediaBrowser.Server.Implementations.FileOrganization; using MediaBrowser.Server.Implementations.HttpServer; +using MediaBrowser.Server.Implementations.HttpServer.Security; using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; using MediaBrowser.Server.Implementations.LiveTv; @@ -598,7 +599,7 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); - HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html"); + HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", WebApplicationName, "dashboard/index.html"); RegisterSingleInstance(HttpServer, false); progress.Report(10); @@ -667,6 +668,11 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + var authContext = new AuthorizationContext(); + RegisterSingleInstance(authContext); + RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); + RegisterSingleInstance(new AuthService()); + RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs index 77a543ad7f..4ba98e9b69 100644 --- a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.ServerApplication.Native public static void OpenDashboardPage(string page, User loggedInUser, IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger) { var url = "http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + - appHost.WebApplicationName + "/dashboard/" + page; + appHost.WebApplicationName + "/web/" + page; OpenUrl(url, logger); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 2ca0669cd6..80f864a326 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPages /// [Route("/dashboard/ConfigurationPages", "GET")] + [Route("/web/ConfigurationPages", "GET")] public class GetDashboardConfigurationPages : IReturn> { /// @@ -38,6 +39,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPage /// [Route("/dashboard/ConfigurationPage", "GET")] + [Route("/web/ConfigurationPage", "GET")] public class GetDashboardConfigurationPage { /// @@ -50,6 +52,7 @@ namespace MediaBrowser.WebDashboard.Api /// /// Class GetDashboardResource /// + [Route("/web/{ResourceName*}", "GET")] [Route("/dashboard/{ResourceName*}", "GET")] public class GetDashboardResource { diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 4a71d9fd34..9db0cc8fc7 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.XbmcMetadata.Providers protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return directoryService.GetFile(Path.Combine(info.Path, "series.nfo")); + return directoryService.GetFile(Path.Combine(info.Path, "tvshow.nfo")); } } }