2
0
Эх сурвалжийг харах

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

Luke Pulverenti 11 жил өмнө
parent
commit
389390b82e
39 өөрчлөгдсөн 587 нэмэгдсэн , 267 устгасан
  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"));
         }
     }
 }