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

This commit is contained in:
Luke Pulverenti 2014-07-02 00:57:18 -04:00
parent 3bef6ead9c
commit 389390b82e
39 changed files with 587 additions and 267 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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>

View File

@ -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 = '\\';

View File

@ -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>

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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" />

View File

@ -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;

View File

@ -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
{

View File

@ -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);

View File

@ -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) ||

View File

@ -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;
}

View File

@ -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" />

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,9 @@
using ServiceStack.Web;
namespace MediaBrowser.Controller.Net
{
public interface IAuthService
{
void Authenticate(IRequest request, IResponse response, object requestDto);
}
}

View File

@ -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);
}
}

View File

@ -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; }
}
}

View File

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

View File

@ -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; }
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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; }
}
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -228,5 +228,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
public string StatusDescription { get; set; }
public int PaddingLength { get; set; }
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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" />

View File

@ -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));

View File

@ -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);
}

View File

@ -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
{

View File

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