From c02e917f5657db4bd76fc6ca17c535fc441c641c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 7 Jul 2014 21:41:03 -0400 Subject: [PATCH] completed auth database --- MediaBrowser.Api/ApiEntryPoint.cs | 34 +- MediaBrowser.Api/ConfigurationService.cs | 1 + .../Playback/Hls/BaseHlsService.cs | 9 +- .../Playback/Hls/DynamicHlsService.cs | 3 +- .../Playback/Hls/HlsSegmentService.cs | 2 +- MediaBrowser.Api/SessionsService.cs | 18 +- MediaBrowser.Api/SystemService.cs | 1 + MediaBrowser.Api/UserService.cs | 32 +- .../Channels/ChannelItemInfo.cs | 13 +- .../Collections/CollectionEvents.cs | 37 ++ .../Collections/ICollectionManager.cs | 15 + MediaBrowser.Controller/Dto/IDtoService.cs | 7 + .../MediaBrowser.Controller.csproj | 4 + .../Providers/IMetadataProvider.cs | 1 + .../Security/AuthenticationInfo.cs | 61 ++++ .../Security/AuthenticationInfoQuery.cs | 42 +++ .../Security/IAuthenticationRepository.cs | 39 ++ .../Session/ISessionManager.cs | 25 +- .../Session/SessionInfo.cs | 2 +- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 10 +- MediaBrowser.Model/ApiClient/IApiClient.cs | 19 +- .../Configuration/ServerConfiguration.cs | 2 + .../Users/AuthenticationResult.cs | 2 +- .../Manager/MetadataService.cs | 1 - .../Manager/ProviderUtils.cs | 2 +- .../TV/MovieDbEpisodeImageProvider.cs | 2 +- .../Channels/ChannelManager.cs | 3 +- .../Collections/CollectionManager.cs | 57 ++- .../HttpServer/Security/AuthService.cs | 48 +-- ...MediaBrowser.Server.Implementations.csproj | 1 + .../SqliteFileOrganizationRepository.cs | 2 +- .../Security/AuthenticationRepository.cs | 338 ++++++++++++++++++ .../Session/SessionManager.cs | 159 +++++++- .../ApplicationHost.cs | 20 +- .../Parsers/BaseNfoParser.cs | 2 +- .../Providers/BaseNfoProvider.cs | 10 +- .../Savers/SeasonXmlSaver.cs | 13 +- .../Savers/XmlSaverHelpers.cs | 22 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 41 files changed, 933 insertions(+), 136 deletions(-) create mode 100644 MediaBrowser.Controller/Collections/CollectionEvents.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfo.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs create mode 100644 MediaBrowser.Controller/Security/IAuthenticationRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 8e6ca44014..f255339bc3 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; - public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); + public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); /// /// Initializes a new instance of the class. @@ -102,7 +102,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, FileDeleteMode.All)); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -295,17 +295,18 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; - KillTranscodingJob(job, FileDeleteMode.All); + KillTranscodingJob(job, path => true); } /// /// Kills the single transcoding job. /// /// The device id. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// sourcePath - internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -330,12 +331,12 @@ namespace MediaBrowser.Api { await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); } - + try { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -352,10 +353,11 @@ namespace MediaBrowser.Api /// /// The device identifier. /// The type. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// deviceId - internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -385,7 +387,7 @@ namespace MediaBrowser.Api { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -401,8 +403,8 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// /// The job. - /// The delete mode. - private void KillTranscodingJob(TranscodingJob job, FileDeleteMode deleteMode) + /// The delete. + private void KillTranscodingJob(TranscodingJob job, Func delete) { lock (_activeTranscodingJobs) { @@ -454,7 +456,7 @@ namespace MediaBrowser.Api } } - if (deleteMode == FileDeleteMode.All) + if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } @@ -593,10 +595,4 @@ namespace MediaBrowser.Api /// Hls } - - public enum FileDeleteMode - { - None, - All - } } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index e628c5f7af..6710461ad9 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetConfiguration /// [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] + [Authenticated] public class GetConfiguration : IReturn { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 8a65e2b565..8eff75533e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -22,7 +22,8 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } @@ -103,8 +104,8 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); - + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => true, false).ConfigureAwait(false); + // If the playlist doesn't already exist, startup ffmpeg try { @@ -252,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; - + var itsOffsetMs = hlsVideoRequest == null ? 0 : hlsVideoRequest.TimeStampOffsetMs; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 6c09f00a17..07aaf5f86c 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -127,8 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - // TODO: Delete files from other jobs, but not this one - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.None, false).ConfigureAwait(false); + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 3848cb2de0..f283525885 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); + var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true); Task.WaitAll(task); } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index f4651601b0..ed7db626f9 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -213,6 +213,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] public class PostCapabilities : IReturnVoid { /// @@ -235,6 +236,11 @@ namespace MediaBrowser.Api public bool SupportsMediaControl { get; set; } } + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + /// /// Class SessionsService /// @@ -246,16 +252,26 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; /// /// Initializes a new instance of the class. /// /// The session manager. /// The user manager. - public SessionsService(ISessionManager sessionManager, IUserManager userManager) + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext) { _sessionManager = sessionManager; _userManager = userManager; + _authContext = authContext; + } + + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); } /// diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 6f2e83a796..f336736be8 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Api /// Class GetSystemInfo /// [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] public class GetSystemInfo : IReturn { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index cda489c94a..bcaf80d69c 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using ServiceStack; using ServiceStack.Text.Controller; @@ -19,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetUsers /// [Route("/Users", "GET", Summary = "Gets a list of users")] + [Authenticated] public class GetUsers : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -37,6 +37,7 @@ namespace MediaBrowser.Api /// Class GetUser /// [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] + [Authenticated] public class GetUser : IReturn { /// @@ -159,11 +160,6 @@ namespace MediaBrowser.Api /// public class UserService : BaseApiService, IHasAuthorization { - /// - /// The _XML serializer - /// - private readonly IXmlSerializer _xmlSerializer; - /// /// The _user manager /// @@ -176,19 +172,12 @@ namespace MediaBrowser.Api /// /// Initializes a new instance of the class. /// - /// The XML serializer. /// The user manager. /// The dto service. + /// The session mananger. /// xmlSerializer - public UserService(IXmlSerializer xmlSerializer, IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) - : base() + public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) { - if (xmlSerializer == null) - { - throw new ArgumentNullException("xmlSerializer"); - } - - _xmlSerializer = xmlSerializer; _userManager = userManager; _dtoService = dtoService; _sessionMananger = sessionMananger; @@ -196,6 +185,11 @@ namespace MediaBrowser.Api public object Get(GetPublicUsers request) { + if (!Request.IsLocal && !_sessionMananger.IsLocal(Request.RemoteIp)) + { + return ToOptimizedResult(new List()); + } + return Get(new GetUsers { IsHidden = false, @@ -368,9 +362,15 @@ namespace MediaBrowser.Api { throw new ArgumentException("There must be at least one enabled user in the system."); } + + var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); + + Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : + _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index 707807bd5a..3c7df91c1c 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Channels { public string Name { get; set; } + public string SeriesName { get; set; } + public string Id { get; set; } public ChannelItemType Type { get; set; } @@ -28,8 +30,6 @@ namespace MediaBrowser.Controller.Channels public long? RunTimeTicks { get; set; } - public bool IsInfiniteStream { get; set; } - public string ImageUrl { get; set; } public ChannelMediaType MediaType { get; set; } @@ -43,9 +43,14 @@ namespace MediaBrowser.Controller.Channels public int? ProductionYear { get; set; } public DateTime? DateCreated { get; set; } - + + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } + public List MediaSources { get; set; } - + + public bool IsInfiniteStream { get; set; } + public ChannelItemInfo() { MediaSources = new List(); diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionEvents.cs new file mode 100644 index 0000000000..80f66a444a --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionEvents.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } + + public class CollectionModifiedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the items changed. + /// + /// The items changed. + public List ItemsChanged { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index fdb2a49755..9130f68d43 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -8,6 +8,21 @@ namespace MediaBrowser.Controller.Collections { public interface ICollectionManager { + /// + /// Occurs when [collection created]. + /// + event EventHandler CollectionCreated; + + /// + /// Occurs when [items added to collection]. + /// + event EventHandler ItemsAddedToCollection; + + /// + /// Occurs when [items removed from collection]. + /// + event EventHandler ItemsRemovedFromCollection; + /// /// Creates the collection. /// diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 0d0555dc0d..434e896dad 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -50,6 +50,13 @@ namespace MediaBrowser.Controller.Dto /// ChapterInfoDto. ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item); + /// + /// Gets the user item data dto. + /// + /// The data. + /// UserItemDataDto. + UserItemDataDto GetUserItemDataDto(UserItemData data); + /// /// Gets the item by name dto. /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ad3033f9e..1c60ea8e68 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -95,6 +95,7 @@ + @@ -233,6 +234,9 @@ + + + diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index d33b2c9eb0..52cd6fceac 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs new file mode 100644 index 0000000000..dd5eec1f92 --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -0,0 +1,61 @@ +using System; + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfo + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string AppName { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string DeviceName { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// true if this instance is active; otherwise, false. + public bool IsActive { get; set; } + + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date revoked. + /// + /// The date revoked. + public DateTime? DateRevoked { get; set; } + } +} diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs new file mode 100644 index 0000000000..3234b0350f --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs @@ -0,0 +1,42 @@ + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfoQuery + { + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// null if [is active] contains no value, true if [is active]; otherwise, false. + public bool? IsActive { get; set; } + + /// + /// Gets or sets the start index. + /// + /// The start index. + public int? StartIndex { get; set; } + + /// + /// Gets or sets the limit. + /// + /// The limit. + public int? Limit { get; set; } + } +} diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs new file mode 100644 index 0000000000..219b07028d --- /dev/null +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -0,0 +1,39 @@ +using MediaBrowser.Model.Querying; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Security +{ + public interface IAuthenticationRepository + { + /// + /// Creates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Create(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Updates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Update(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Gets the specified query. + /// + /// The query. + /// QueryResult{AuthenticationInfo}. + QueryResult Get(AuthenticationInfoQuery query); + + /// + /// Gets the specified identifier. + /// + /// The identifier. + /// AuthenticationInfo. + AuthenticationInfo Get(string id); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 7b20621827..4b30c964c8 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -259,7 +259,28 @@ namespace MediaBrowser.Controller.Session /// /// Validates the security token. /// - /// The token. - void ValidateSecurityToken(string token); + /// The access token. + void ValidateSecurityToken(string accessToken); + + /// + /// Logouts the specified access token. + /// + /// The access token. + /// Task. + Task Logout(string accessToken); + + /// + /// Revokes the user tokens. + /// + /// The user identifier. + /// Task. + Task RevokeUserTokens(string userId); + + /// + /// Determines whether the specified remote endpoint is local. + /// + /// The remote endpoint. + /// true if the specified remote endpoint is local; otherwise, false. + bool IsLocal(string remoteEndpoint); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6f27f6cb28..58d455955a 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Session public List SupportedCommands { get; set; } public TranscodingInfo TranscodingInfo { get; set; } - + /// /// Gets a value indicating whether this instance is active. /// diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index cc9bc7bed7..62aec5ecbd 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -1,11 +1,11 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.LocalMetadata { diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 33c6f980ce..74a3314b72 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -246,7 +246,7 @@ namespace MediaBrowser.Model.ApiClient /// Gets the client session asynchronous. /// /// Task{SessionInfoDto}. - Task GetCurrentSessionAsync(); + Task GetCurrentSessionAsync(CancellationToken cancellationToken); /// /// Gets the item counts async. @@ -644,6 +644,13 @@ namespace MediaBrowser.Model.ApiClient /// Task. Task SetVolume(string sessionId, int volume); + /// + /// Stops the transcoding processes. + /// + /// The device identifier. + /// Task. + Task StopTranscodingProcesses(string deviceId); + /// /// Sets the index of the audio stream. /// @@ -984,7 +991,7 @@ namespace MediaBrowser.Model.ApiClient /// The query. /// The cancellation token. /// Task{LiveTvInfo}. - Task> GetLiveTvChannelsAsync(ChannelQuery query, CancellationToken cancellationToken); + Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken); /// /// Gets the live tv channel asynchronous. @@ -1187,5 +1194,13 @@ namespace MediaBrowser.Model.ApiClient /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken); + + /// + /// Gets the latest channel items. + /// + /// The query. + /// The cancellation token. + /// Task{QueryResult{BaseItemDto}}. + Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 49b731341a..d9404ce29e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.Model.Configuration public bool DefaultMetadataSettingsApplied { get; set; } + public bool EnableTokenAuthentication { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index 8046e83c7f..97fe2ea99e 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -21,6 +21,6 @@ namespace MediaBrowser.Model.Users /// Gets or sets the authentication token. /// /// The authentication token. - public string AuthenticationToken { get; set; } + public string AccessToken { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 95eca6ba0c..7feca2d349 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 2783fda6bc..f09890c40f 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Manager var key = id.Key; // Don't replace existing Id's. - if (!target.ProviderIds.ContainsKey(key)) + if (replaceData || !target.ProviderIds.ContainsKey(key)) { target.ProviderIds[key] = id.Value; } diff --git a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs index 7979711ec0..b3f62005b0 100644 --- a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs @@ -19,7 +19,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder + public class MovieDbEpisodeImageProvider/* : IRemoteImageProvider, IHasOrder*/ { private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index c6dd807585..d0ea64e0fc 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1133,6 +1133,8 @@ namespace MediaBrowser.Server.Implementations.Channels item.CommunityRating = info.CommunityRating; item.OfficialRating = info.OfficialRating; item.Overview = info.Overview; + item.IndexNumber = info.IndexNumber; + item.ParentIndexNumber = info.ParentIndexNumber; item.People = info.People; item.PremiereDate = info.PremiereDate; item.ProductionYear = info.ProductionYear; @@ -1159,7 +1161,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (channelMediaItem != null) { - channelMediaItem.IsInfiniteStream = info.IsInfiniteStream; channelMediaItem.ContentType = info.ContentType; channelMediaItem.ChannelMediaSources = info.MediaSources; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 728b18bbf4..7bc7838c6f 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; using MoreLinq; using System; using System.Collections.Generic; @@ -19,12 +21,18 @@ namespace MediaBrowser.Server.Implementations.Collections private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger _logger; - public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) + public event EventHandler CollectionCreated; + public event EventHandler ItemsAddedToCollection; + public event EventHandler ItemsRemovedFromCollection; + + public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILogger logger) { _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; + _logger = logger; } public Folder GetCollectionsFolder(string userId) @@ -74,9 +82,16 @@ namespace MediaBrowser.Server.Implementations.Collections if (options.ItemIdList.Count > 0) { - await AddToCollection(collection.Id, options.ItemIdList); + await AddToCollection(collection.Id, options.ItemIdList, false); } + EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs + { + Collection = collection, + Options = options + + }, _logger); + return collection; } finally @@ -113,7 +128,12 @@ namespace MediaBrowser.Server.Implementations.Collections return GetCollectionsFolder(string.Empty); } - public async Task AddToCollection(Guid collectionId, IEnumerable ids) + public Task AddToCollection(Guid collectionId, IEnumerable ids) + { + return AddToCollection(collectionId, ids, true); + } + + private async Task AddToCollection(Guid collectionId, IEnumerable ids, bool fireEvent) { var collection = _libraryManager.GetItemById(collectionId) as BoxSet; @@ -123,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); var currentLinkedChildren = collection.GetLinkedChildren().ToList(); foreach (var itemId in ids) @@ -134,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("No item exists with the supplied Id"); } + itemList.Add(item); + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); @@ -165,6 +188,16 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + if (fireEvent) + { + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); + } } public async Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds) @@ -177,6 +210,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); foreach (var itemId in itemIds) { @@ -190,6 +224,12 @@ namespace MediaBrowser.Server.Implementations.Collections list.Add(child); var childItem = _libraryManager.GetItemById(itemId); + + if (childItem != null) + { + itemList.Add(childItem); + } + var supportsGrouping = childItem as ISupportsBoxSetGrouping; if (supportsGrouping != null) @@ -221,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Collections { File.Delete(file); } - + foreach (var child in list) { collection.LinkedChildren.Remove(child); @@ -238,6 +278,13 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); } public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index c29a7d14e3..6894d7ac7e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; @@ -13,9 +13,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { - public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext) + private readonly IServerConfigurationManager _config; + + public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config) { AuthorizationContext = authorizationContext; + _config = config; SessionManager = sessionManager; UserManager = userManager; } @@ -54,28 +57,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security //This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(req); - if (string.IsNullOrWhiteSpace(auth.Token)) + if (!string.IsNullOrWhiteSpace(auth.Token) || _config.Configuration.EnableTokenAuthentication) { - // Legacy - // TODO: Deprecate this in Oct 2014 - - User user = null; - - if (!string.IsNullOrWhiteSpace(auth.UserId)) - { - var userId = auth.UserId; + SessionManager.ValidateSecurityToken(auth.Token); + } - user = UserManager.GetUserById(new Guid(userId)); - } + var user = string.IsNullOrWhiteSpace(auth.UserId) + ? null + : UserManager.GetUserById(new Guid(auth.UserId)); - if (user == null || user.Configuration.IsDisabled) - { - throw new UnauthorizedAccessException("Unauthorized access."); - } + if (user != null && user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); } - else + + if (!string.IsNullOrWhiteSpace(auth.DeviceId) && + !string.IsNullOrWhiteSpace(auth.Client) && + !string.IsNullOrWhiteSpace(auth.Device)) { - SessionManager.ValidateSecurityToken(auth.Token); + SessionManager.LogSessionActivity(auth.Client, + auth.Version, + auth.DeviceId, + auth.Device, + req.RemoteIp, + user); } } @@ -108,11 +113,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security } } - private void LogRequest() - { - - } - protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) { var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 1d201e0690..859011f6ee 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -216,6 +216,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs index df32ac021b..5d5855bf8b 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { - public class SqliteFileOrganizationRepository : IFileOrganizationRepository + public class SqliteFileOrganizationRepository : IFileOrganizationRepository, IDisposable { private IDbConnection _connection; diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs new file mode 100644 index 0000000000..5f225ddd47 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs @@ -0,0 +1,338 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Server.Implementations.Persistence; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Security +{ + public class AuthenticationRepository : IAuthenticationRepository + { + private IDbConnection _connection; + private readonly ILogger _logger; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private IDbCommand _saveInfoCommand; + + public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveInfoCommand = _connection.CreateCommand(); + _saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)"; + + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked"); + } + + public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) + { + info.Id = Guid.NewGuid().ToString("N"); + + return Update(info, cancellationToken); + } + + public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id); + _saveInfoCommand.GetParameter(index++).Value = info.AccessToken; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceId; + _saveInfoCommand.GetParameter(index++).Value = info.AppName; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceName; + _saveInfoCommand.GetParameter(index++).Value = info.UserId; + _saveInfoCommand.GetParameter(index++).Value = info.IsActive; + _saveInfoCommand.GetParameter(index++).Value = info.DateCreated; + _saveInfoCommand.GetParameter(index++).Value = info.DateRevoked; + + _saveInfoCommand.Transaction = transaction; + + _saveInfoCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + + private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText; + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (startIndex > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens ORDER BY DateCreated LIMIT {0})", + startIndex.ToString(_usCulture))); + } + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=@AccessToken"); + cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken; + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=@UserId"); + cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId; + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=@DeviceId"); + cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId; + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=@IsActive"); + cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value; + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from AccessTokens"; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(Get(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText + " where Id=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + return Get(reader); + } + } + } + + return null; + } + + private AuthenticationInfo Get(IDataReader reader) + { + var s = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + var info = new AuthenticationInfo + { + Id = reader.GetGuid(0).ToString("N"), + AccessToken = reader.GetString(1) + }; + + if (!reader.IsDBNull(2)) + { + info.DeviceId = reader.GetString(2); + } + + if (!reader.IsDBNull(3)) + { + info.AppName = reader.GetString(3); + } + + if (!reader.IsDBNull(4)) + { + info.DeviceName = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + info.UserId = reader.GetString(5); + } + + info.IsActive = reader.GetBoolean(6); + info.DateCreated = reader.GetDateTime(7); + + if (!reader.IsDBNull(8)) + { + info.DateRevoked = reader.GetDateTime(8); + } + + return info; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + try + { + lock (_disposeLock) + { + if (_connection != null) + { + if (_connection.IsOpen()) + { + _connection.Close(); + } + + _connection.Dispose(); + _connection = null; + } + } + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing database", ex); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 784719318f..c3d24c0de1 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,6 +1,4 @@ -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Common.Events; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -13,12 +11,14 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Users; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,7 +27,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Server.Implementations.Session { @@ -62,6 +61,8 @@ namespace MediaBrowser.Server.Implementations.Session private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; + private readonly IAuthenticationRepository _authRepo; + /// /// Gets or sets the configuration manager. /// @@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session /// The logger. /// The user repository. /// The library manager. - public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient) + public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo) { _userDataRepository = userDataRepository; _configurationManager = configurationManager; @@ -119,6 +120,7 @@ namespace MediaBrowser.Server.Implementations.Session _jsonSerializer = jsonSerializer; _appHost = appHost; _httpClient = httpClient; + _authRepo = authRepo; } /// @@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session /// Task. /// user /// - public async Task LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + public async Task LogSessionActivity(string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { if (string.IsNullOrEmpty(clientType)) { @@ -1157,7 +1164,37 @@ namespace MediaBrowser.Server.Implementations.Session public void ValidateSecurityToken(string token) { + if (string.IsNullOrWhiteSpace(token)) + { + throw new UnauthorizedAccessException(); + } + var result = _authRepo.Get(new AuthenticationInfoQuery + { + AccessToken = token + }); + + var info = result.Items.FirstOrDefault(); + + if (info == null) + { + throw new UnauthorizedAccessException(); + } + + if (!info.IsActive) + { + throw new UnauthorizedAccessException("Access token has expired."); + } + + if (!string.IsNullOrWhiteSpace(info.UserId)) + { + var user = _userManager.GetUserById(new Guid(info.UserId)); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); + } + } } /// @@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session /// public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) { - var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { @@ -1185,6 +1222,8 @@ namespace MediaBrowser.Server.Implementations.Session var user = _userManager.Users .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false); + var session = await LogSessionActivity(clientType, appVersion, deviceId, @@ -1197,11 +1236,108 @@ namespace MediaBrowser.Server.Implementations.Session { User = _dtoService.GetUserDto(user), SessionInfo = GetSessionInfoDto(session), - AuthenticationToken = Guid.NewGuid().ToString("N") + AccessToken = token }; } - private bool IsLocal(string remoteEndpoint) + private async Task GetAuthorizationToken(string userId, string deviceId, string app, string deviceName) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = deviceId, + IsActive = true, + UserId = userId, + Limit = 1 + }); + + if (existing.Items.Length > 0) + { + _logger.Debug("Reissuing access token"); + return existing.Items[0].AccessToken; + } + + var newToken = new AuthenticationInfo + { + AppName = app, + DateCreated = DateTime.UtcNow, + DeviceId = deviceId, + DeviceName = deviceName, + UserId = userId, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N") + }; + + _logger.Debug("Creating new access token for user {0}", userId); + await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false); + + return newToken.AccessToken; + } + + public async Task Logout(string accessToken) + { + if (string.IsNullOrWhiteSpace(accessToken)) + { + throw new ArgumentNullException("accessToken"); + } + + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + Limit = 1, + AccessToken = accessToken + + }).Items.FirstOrDefault(); + + if (existing != null) + { + existing.IsActive = false; + + await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try + { + ReportSessionEnded(session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended", ex); + } + } + } + } + + public async Task RevokeUserTokens(string userId) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true, + UserId = userId + }); + + foreach (var info in existing.Items) + { + await Logout(info.AccessToken).ConfigureAwait(false); + } + } + + private bool IsLocalhost(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + } + + public bool IsLocal(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { @@ -1211,12 +1347,11 @@ namespace MediaBrowser.Server.Implementations.Session // Private address space: // http://en.wikipedia.org/wiki/Private_network - return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + return IsLocalhost(remoteEndpoint) || remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 91e92e21c5..ca04e580f3 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.ServerApplication private IUserViewManager UserViewManager { get; set; } + private IAuthenticationRepository AuthenticationRepository { get; set; } + /// /// Initializes a new instance of the class. /// @@ -586,6 +588,9 @@ namespace MediaBrowser.ServerApplication FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); RegisterSingleInstance(FileOrganizationRepository); + AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); + RegisterSingleInstance(AuthenticationRepository); + UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer); RegisterSingleInstance(UserManager); @@ -625,7 +630,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); RegisterSingleInstance(DtoService); - SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient); + SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository); RegisterSingleInstance(SessionManager); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); @@ -651,7 +656,7 @@ namespace MediaBrowser.ServerApplication var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient); RegisterSingleInstance(connectionManager); - var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); RegisterSingleInstance(collectionManager); LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); @@ -678,7 +683,7 @@ namespace MediaBrowser.ServerApplication var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext)); + RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext, ServerConfigurationManager)); RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); @@ -755,6 +760,15 @@ namespace MediaBrowser.ServerApplication return repo; } + private async Task GetAuthenticationRepository() + { + var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); + + await repo.Initialize().ConfigureAwait(false); + + return repo; + } + /// /// Configures the repositories. /// diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 0515148f02..49851f42a7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The cancellation token. private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(metadataFile, encoding)) + using (var streamReader = new StreamReader(metadataFile)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs index 65648e1d05..47371ea04b 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Providers var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -44,10 +44,10 @@ namespace MediaBrowser.XbmcMetadata.Providers { result.HasMetadata = false; } - finally - { - XmlProviderUtils.XmlParsingResourcePool.Release(); - } + //finally + //{ + // XmlProviderUtils.XmlParsingResourcePool.Release(); + //} return result; } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs index 1b6c7a3401..a96b0636f2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs @@ -1,15 +1,14 @@ -using System.Collections.Generic; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security; using System.Text; using System.Threading; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; namespace MediaBrowser.XbmcMetadata.Savers { diff --git a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs index 7f817f5912..7111a429ab 100644 --- a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security; -using System.Text; -using System.Xml; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -18,6 +10,14 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.XbmcMetadata.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; namespace MediaBrowser.XbmcMetadata.Savers { @@ -392,9 +392,9 @@ namespace MediaBrowser.XbmcMetadata.Savers builder.Append("" + SecurityElement.Escape(person) + ""); } - if (writers.Count > 0) + foreach (var person in writers) { - builder.Append("" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + ""); + builder.Append("" + SecurityElement.Escape(person) + ""); } var hasTrailer = item as IHasTrailers; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 8c83ffabcd..f9e8c5aad8 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.412 + 3.0.414 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 88431b72cd..89aeff0a3d 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.412 + 3.0.414 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 73f764fd5e..1696ee347f 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.412 + 3.0.414 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +