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