aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-07-07 21:41:03 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-07-07 21:41:03 -0400
commitc02e917f5657db4bd76fc6ca17c535fc441c641c (patch)
treeb09ae21bd9c5f15cc9594eba27f5c982919c1872 /MediaBrowser.Server.Implementations
parent379fa002288032097ff3222c7484136f10ab69d2 (diff)
completed auth database
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Collections/CollectionManager.cs57
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs48
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj1
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs338
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs159
7 files changed, 565 insertions, 43 deletions
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index c6dd80758..d0ea64e0f 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 728b18bbf..7bc7838c6 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<CollectionCreatedEventArgs> CollectionCreated;
+ public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+ public event EventHandler<CollectionModifiedEventArgs> 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<Guid> ids)
+ public Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
+ {
+ return AddToCollection(collectionId, ids, true);
+ }
+
+ private async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@@ -123,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Collections
}
var list = new List<LinkedChild>();
+ var itemList = new List<BaseItem>();
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<Guid> itemIds)
@@ -177,6 +210,7 @@ namespace MediaBrowser.Server.Implementations.Collections
}
var list = new List<LinkedChild>();
+ var itemList = new List<BaseItem>();
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<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
index c29a7d14e..6894d7ac7 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 1d201e069..859011f6e 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -216,6 +216,7 @@
<Compile Include="ScheduledTasks\ChapterImagesTask.cs" />
<Compile Include="ScheduledTasks\RefreshIntrosTask.cs" />
<Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
+ <Compile Include="Security\AuthenticationRepository.cs" />
<Compile Include="Security\EncryptionManager.cs" />
<Compile Include="ServerApplicationPaths.cs" />
<Compile Include="ServerManager\ServerManager.cs" />
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
index df32ac021..5d5855bf8 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 000000000..5f225ddd4
--- /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<AuthenticationInfo> Get(AuthenticationInfoQuery query)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = BaseSelectText;
+
+ var whereClauses = new List<string>();
+
+ 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<AuthenticationInfo>();
+ 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<AuthenticationInfo>()
+ {
+ 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;
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private readonly object _disposeLock = new object();
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ 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 784719318..c3d24c0de 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;
+
/// <summary>
/// Gets or sets the configuration manager.
/// </summary>
@@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="logger">The logger.</param>
/// <param name="userRepository">The user repository.</param>
/// <param name="libraryManager">The library manager.</param>
- 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;
}
/// <summary>
@@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">user</exception>
/// <exception cref="System.UnauthorizedAccessException"></exception>
- public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
+ public async Task<SessionInfo> 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.");
+ }
+ }
}
/// <summary>
@@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <exception cref="UnauthorizedAccessException"></exception>
public async Task<AuthenticationResult> 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<string> 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);
}
/// <summary>