Browse Source

fixes #789 - Security Issue: API allows access to any folder of the PC running MediaBrowser

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
389390b82e
39 tập tin đã thay đổi với 587 bổ sung267 xóa
  1. 2 0
      MediaBrowser.Api/AppThemeService.cs
  2. 0 190
      MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
  3. 6 7
      MediaBrowser.Api/BaseApiService.cs
  4. 3 1
      MediaBrowser.Api/ChannelService.cs
  5. 3 1
      MediaBrowser.Api/DisplayPreferencesService.cs
  6. 2 0
      MediaBrowser.Api/EnvironmentService.cs
  7. 2 0
      MediaBrowser.Api/GamesService.cs
  8. 1 1
      MediaBrowser.Api/Library/LibraryService.cs
  9. 1 1
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  10. 0 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  11. 0 7
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  12. 8 8
      MediaBrowser.Api/SessionsService.cs
  13. 5 5
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  14. 5 2
      MediaBrowser.Api/UserService.cs
  15. 6 15
      MediaBrowser.Controller/Entities/Folder.cs
  16. 8 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  17. 41 0
      MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
  18. 32 0
      MediaBrowser.Controller/Net/AuthorizationInfo.cs
  19. 9 0
      MediaBrowser.Controller/Net/IAuthService.cs
  20. 14 0
      MediaBrowser.Controller/Net/IAuthorizationContext.cs
  21. 12 0
      MediaBrowser.Controller/Net/IHasAuthorization.cs
  22. 1 2
      MediaBrowser.Controller/Net/IHasResultFactory.cs
  23. 12 0
      MediaBrowser.Controller/Net/IHasSession.cs
  24. 1 0
      MediaBrowser.Controller/Net/IRestfulService.cs
  25. 13 0
      MediaBrowser.Controller/Net/ISessionContext.cs
  26. 73 0
      MediaBrowser.Controller/Net/LoggedAttribute.cs
  27. 7 3
      MediaBrowser.Providers/Manager/MetadataService.cs
  28. 2 6
      MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
  29. 20 14
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  30. 2 0
      MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
  31. 113 0
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
  32. 96 0
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  33. 35 0
      MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs
  34. 36 0
      MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs
  35. 4 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  36. 7 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  37. 1 1
      MediaBrowser.ServerApplication/Native/BrowserLauncher.cs
  38. 3 0
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  39. 1 1
      MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs

+ 2 - 0
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;

+ 0 - 190
MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs

@@ -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
-        /// <summary>
-        /// Gets or sets the user manager.
-        /// </summary>
-        /// <value>The user manager.</value>
-        public IUserManager UserManager { get; set; }
-
-        public ISessionManager SessionManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        public ILogger Logger { get; set; }
-
-        /// <summary>
-        /// The request filter is executed before the service.
-        /// </summary>
-        /// <param name="request">The http request wrapper</param>
-        /// <param name="response">The http response wrapper</param>
-        /// <param name="requestDto">The request DTO</param>
-        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);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the auth.
-        /// </summary>
-        /// <param name="httpReq">The HTTP req.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private static Dictionary<string, string> 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));
-        }
-
-        /// <summary>
-        /// Gets the authorization.
-        /// </summary>
-        /// <param name="httpReq">The HTTP req.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        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
-            };
-        }
-
-        /// <summary>
-        /// Gets the authorization.
-        /// </summary>
-        /// <param name="authorizationHeader">The authorization header.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private static Dictionary<string, string> 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<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            foreach (var item in parts)
-            {
-                var param = item.Trim().Split(new[] { '=' }, 2);
-                result.Add(param[0], param[1].Trim(new[] { '"' }));
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// A new shallow copy of this filter is used on every request.
-        /// </summary>
-        /// <returns>IHasRequestFilter.</returns>
-        public IHasRequestFilter Copy()
-        {
-            return this;
-        }
-
-        /// <summary>
-        /// Order in which Request Filters are executed.
-        /// &lt;0 Executed before global request filters
-        /// &gt;0 Executed after global request filters
-        /// </summary>
-        /// <value>The priority.</value>
-        public int Priority
-        {
-            get { return 0; }
-        }
-    }
-
-    public class AuthorizationInfo
-    {
-        public string UserId;
-        public string DeviceId;
-        public string Device;
-        public string Client;
-        public string Version;
-    }
-}

+ 6 - 7
MediaBrowser.Api/BaseApiService.cs

@@ -14,8 +14,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// Class BaseApiService
     /// </summary>
-    [AuthorizationRequestFilter]
-    public class BaseApiService : IHasResultFactory, IRestfulService
+    public class BaseApiService : IHasResultFactory, IRestfulService, IHasSession
     {
         /// <summary>
         /// Gets or sets the logger.
@@ -35,6 +34,8 @@ namespace MediaBrowser.Api
         /// <value>The request context.</value>
         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
         /// <summary>
         /// Gets the session.
         /// </summary>
-        /// <param name="sessionManager">The session manager.</param>
         /// <returns>SessionInfo.</returns>
-        protected SessionInfo GetSession(ISessionManager sessionManager)
+        /// <exception cref="System.ArgumentException">Session not found.</exception>
+        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)
             {

+ 3 - 1
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;

+ 3 - 1
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
     /// <summary>
     /// Class DisplayPreferencesService
     /// </summary>
+    [Authenticated]
     public class DisplayPreferencesService : BaseApiService
     {
         /// <summary>

+ 2 - 0
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
     /// <summary>
     /// Class EnvironmentService
     /// </summary>
+    [Authenticated]
     public class EnvironmentService : BaseApiService
     {
         const char UncSeparator = '\\';

+ 2 - 0
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
     /// <summary>
     /// Class GamesService
     /// </summary>
+    [Authenticated]
     public class GamesService : BaseApiService
     {
         /// <summary>

+ 1 - 1
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)
             {

+ 1 - 1
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)
             {

+ 0 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -79,7 +79,6 @@
     <Compile Include="DefaultTheme\Models.cs" />
     <Compile Include="DisplayPreferencesService.cs" />
     <Compile Include="EnvironmentService.cs" />
-    <Compile Include="AuthorizationRequestFilterAttribute.cs" />
     <Compile Include="GamesService.cs" />
     <Compile Include="IHasItemFields.cs" />
     <Compile Include="Images\ImageByNameService.cs" />

+ 0 - 7
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<MediaStream> mediaStreams = null;
 
             state.ItemType = item.GetType().Name;

+ 8 - 8
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
             {

+ 5 - 5
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);
 

+ 5 - 2
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
     /// <summary>
     /// Class UsersService
     /// </summary>
-    public class UserService : BaseApiService
+    public class UserService : BaseApiService, IHasAuthorization
     {
         /// <summary>
         /// The _XML serializer
@@ -166,6 +167,8 @@ namespace MediaBrowser.Api
         private readonly IDtoService _dtoService;
         private readonly ISessionManager _sessionMananger;
 
+        public IAuthorizationContext AuthorizationContext { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserService" /> class.
         /// </summary>
@@ -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) ||

+ 6 - 15
MediaBrowser.Controller/Entities/Folder.cs

@@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            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
         /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
         /// <param name="list">The list.</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
-        /// <param name="filter">The filter.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
+        private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> 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<BaseItem>();
 
-            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, null);
+            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true);
 
             return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
         }

+ 8 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -195,10 +195,18 @@
     <Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
     <Compile Include="MediaEncoding\MediaStreamSelector.cs" />
     <Compile Include="MediaEncoding\VideoEncodingOptions.cs" />
+    <Compile Include="Net\AuthenticatedAttribute.cs" />
+    <Compile Include="Net\AuthorizationInfo.cs" />
+    <Compile Include="Net\IAuthorizationContext.cs" />
+    <Compile Include="Net\IAuthService.cs" />
+    <Compile Include="Net\IHasAuthorization.cs" />
     <Compile Include="Net\IHasResultFactory.cs" />
+    <Compile Include="Net\IHasSession.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IRestfulService.cs" />
+    <Compile Include="Net\ISessionContext.cs" />
+    <Compile Include="Net\LoggedAttribute.cs" />
     <Compile Include="News\INewsService.cs" />
     <Compile Include="Notifications\INotificationManager.cs" />
     <Compile Include="Notifications\INotificationService.cs" />

+ 41 - 0
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; }
+
+        /// <summary>
+        /// The request filter is executed before the service.
+        /// </summary>
+        /// <param name="request">The http request wrapper</param>
+        /// <param name="response">The http response wrapper</param>
+        /// <param name="requestDto">The request DTO</param>
+        public void RequestFilter(IRequest request, IResponse response, object requestDto)
+        {
+            AuthService.Authenticate(request, response, requestDto);
+        }
+
+        /// <summary>
+        /// A new shallow copy of this filter is used on every request.
+        /// </summary>
+        /// <returns>IHasRequestFilter.</returns>
+        public IHasRequestFilter Copy()
+        {
+            return this;
+        }
+
+        /// <summary>
+        /// Order in which Request Filters are executed.
+        /// &lt;0 Executed before global request filters
+        /// &gt;0 Executed after global request filters
+        /// </summary>
+        /// <value>The priority.</value>
+        public int Priority
+        {
+            get { return 0; }
+        }
+    }
+}

+ 32 - 0
MediaBrowser.Controller/Net/AuthorizationInfo.cs

@@ -0,0 +1,32 @@
+
+namespace MediaBrowser.Controller.Net
+{
+    public class AuthorizationInfo
+    {
+        /// <summary>
+        /// Gets or sets the user identifier.
+        /// </summary>
+        /// <value>The user identifier.</value>
+        public string UserId { get; set; }
+        /// <summary>
+        /// Gets or sets the device identifier.
+        /// </summary>
+        /// <value>The device identifier.</value>
+        public string DeviceId { get; set; }
+        /// <summary>
+        /// Gets or sets the device.
+        /// </summary>
+        /// <value>The device.</value>
+        public string Device { get; set; }
+        /// <summary>
+        /// Gets or sets the client.
+        /// </summary>
+        /// <value>The client.</value>
+        public string Client { get; set; }
+        /// <summary>
+        /// Gets or sets the version.
+        /// </summary>
+        /// <value>The version.</value>
+        public string Version { get; set; }
+    }
+}

+ 9 - 0
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);
+    }
+}

+ 14 - 0
MediaBrowser.Controller/Net/IAuthorizationContext.cs

@@ -0,0 +1,14 @@
+using ServiceStack.Web;
+
+namespace MediaBrowser.Controller.Net
+{
+    public interface IAuthorizationContext
+    {
+        /// <summary>
+        /// Gets the authorization information.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <returns>AuthorizationInfo.</returns>
+        AuthorizationInfo GetAuthorizationInfo(IRequest requestContext);
+    }
+}

+ 12 - 0
MediaBrowser.Controller/Net/IHasAuthorization.cs

@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Controller.Net
+{
+    public interface IHasAuthorization
+    {
+        /// <summary>
+        /// Gets or sets the authorization context.
+        /// </summary>
+        /// <value>The authorization context.</value>
+        IAuthorizationContext AuthorizationContext { get; set; }
+    }
+}

+ 1 - 2
MediaBrowser.Controller/Net/IHasResultFactory.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Net;
-using ServiceStack.Web;
+using ServiceStack.Web;
 
 namespace MediaBrowser.Controller.Net
 {

+ 12 - 0
MediaBrowser.Controller/Net/IHasSession.cs

@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Controller.Net
+{
+    public interface IHasSession
+    {
+        /// <summary>
+        /// Gets or sets the session context.
+        /// </summary>
+        /// <value>The session context.</value>
+        ISessionContext SessionContext { get; set; }
+    }
+}

+ 1 - 0
MediaBrowser.Controller/Net/IRestfulService.cs

@@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Net
     /// <summary>
     /// Interface IRestfulService
     /// </summary>
+    [Logged]
     public interface IRestfulService : IService
     {
     }

+ 13 - 0
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);
+    }
+}

+ 73 - 0
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; }
+
+        /// <summary>
+        /// The request filter is executed before the service.
+        /// </summary>
+        /// <param name="request">The http request wrapper</param>
+        /// <param name="response">The http response wrapper</param>
+        /// <param name="requestDto">The request DTO</param>
+        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);
+                }
+            }
+        }
+
+        /// <summary>
+        /// A new shallow copy of this filter is used on every request.
+        /// </summary>
+        /// <returns>IHasRequestFilter.</returns>
+        public IHasRequestFilter Copy()
+        {
+            return this;
+        }
+
+        /// <summary>
+        /// Order in which Request Filters are executed.
+        /// &lt;0 Executed before global request filters
+        /// &gt;0 Executed after global request filters
+        /// </summary>
+        /// <value>The priority.</value>
+        public int Priority
+        {
+            get { return 0; }
+        }
+    }
+}

+ 7 - 3
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;
+                    }
                 }
             }
 

+ 2 - 6
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

+ 20 - 14
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<ISessionContext>()),
+            }));
+
             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)

+ 2 - 0
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; }
     }
 }

+ 113 - 0
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
+    {
+        /// <summary>
+        /// Restrict authentication to a specific <see cref="IAuthProvider"/>.
+        /// For example, if this attribute should only permit access
+        /// if the user is authenticated with <see cref="BasicAuthProvider"/>,
+        /// you should set this property to <see cref="BasicAuthProvider.Name"/>.
+        /// </summary>
+        public string Provider { get; set; }
+
+        /// <summary>
+        /// Redirect the client to a specific URL if authentication failed.
+        /// If this property is null, simply `401 Unauthorized` is returned.
+        /// </summary>
+        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<ISessionContext>().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<IAuthWithRequest>()
+                .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();
+        }
+    }
+}

+ 96 - 0
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);
+        }
+
+        /// <summary>
+        /// Gets the authorization.
+        /// </summary>
+        /// <param name="httpReq">The HTTP req.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        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
+            };
+        }
+
+        /// <summary>
+        /// Gets the auth.
+        /// </summary>
+        /// <param name="httpReq">The HTTP req.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        private static Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq)
+        {
+            var auth = httpReq.Headers["Authorization"];
+
+            return GetAuthorization(auth);
+        }
+
+        /// <summary>
+        /// Gets the authorization.
+        /// </summary>
+        /// <param name="authorizationHeader">The authorization header.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        private static Dictionary<string, string> 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<string, string>(StringComparer.OrdinalIgnoreCase);
+
+            foreach (var item in parts)
+            {
+                var param = item.Trim().Split(new[] { '=' }, 2);
+                result.Add(param[0], param[1].Trim(new[] { '"' }));
+            }
+
+            return result;
+        }
+    }
+}

+ 35 - 0
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);
+        }
+    }
+}

+ 36 - 0
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);
+        }
+    }
+}

+ 4 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -133,6 +133,7 @@
     <Compile Include="EntryPoints\ServerEventNotifier.cs" />
     <Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
     <Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
+    <Compile Include="HttpServer\Security\AuthorizationContext.cs" />
     <Compile Include="HttpServer\ContainerAdapter.cs" />
     <Compile Include="HttpServer\GetSwaggerResource.cs" />
     <Compile Include="HttpServer\HttpListenerHost.cs" />
@@ -141,9 +142,12 @@
     <Compile Include="HttpServer\NativeWebSocket.cs" />
     <Compile Include="HttpServer\RangeRequestWriter.cs" />
     <Compile Include="HttpServer\ResponseFilter.cs" />
+    <Compile Include="HttpServer\Security\AuthService.cs" />
+    <Compile Include="HttpServer\Security\SessionAuthProvider.cs" />
     <Compile Include="HttpServer\ServerFactory.cs" />
     <Compile Include="HttpServer\ServerLogFactory.cs" />
     <Compile Include="HttpServer\ServerLogger.cs" />
+    <Compile Include="HttpServer\Security\SessionContext.cs" />
     <Compile Include="HttpServer\StreamWriter.cs" />
     <Compile Include="HttpServer\SwaggerService.cs" />
     <Compile Include="Drawing\ImageProcessor.cs" />

+ 7 - 1
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<ISearchEngine>(() => 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<IAuthorizationContext>(authContext);
+            RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
+            RegisterSingleInstance<IAuthService>(new AuthService());
+
             RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder));
 
             var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));

+ 1 - 1
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);
         }

+ 3 - 0
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -25,6 +25,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardConfigurationPages
     /// </summary>
     [Route("/dashboard/ConfigurationPages", "GET")]
+    [Route("/web/ConfigurationPages", "GET")]
     public class GetDashboardConfigurationPages : IReturn<List<ConfigurationPageInfo>>
     {
         /// <summary>
@@ -38,6 +39,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardConfigurationPage
     /// </summary>
     [Route("/dashboard/ConfigurationPage", "GET")]
+    [Route("/web/ConfigurationPage", "GET")]
     public class GetDashboardConfigurationPage
     {
         /// <summary>
@@ -50,6 +52,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// <summary>
     /// Class GetDashboardResource
     /// </summary>
+    [Route("/web/{ResourceName*}", "GET")]
     [Route("/dashboard/{ResourceName*}", "GET")]
     public class GetDashboardResource
     {

+ 1 - 1
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"));
         }
     }
 }