From 9f40c1982bf2b63d2779a8971361364772e33298 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 18 Nov 2016 04:28:39 -0500 Subject: rework additional repositories --- .../Data/BaseSqliteRepository.cs | 24 +- .../Data/SqliteDisplayPreferencesRepository.cs | 228 ++++++++++++++++++ .../Data/SqliteExtensions.cs | 28 ++- .../Emby.Server.Implementations.csproj | 2 + .../Security/AuthenticationRepository.cs | 258 +++++++++++++++++++++ 5 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs create mode 100644 Emby.Server.Implementations/Security/AuthenticationRepository.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index c7ac630a05..8febe83b2e 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.Data //} db.ExecuteAll(string.Join(";", queries)); - + return db; } @@ -119,5 +119,27 @@ namespace Emby.Server.Implementations.Data { } + + protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type) + { + foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) + { + if (row[1].SQLiteType != SQLiteType.Null) + { + var name = row[1].ToString(); + + if (string.Equals(name, columnName, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + + connection.ExecuteAll(string.Join(";", new string[] + { + "alter table " + table, + "add column " + columnName + " " + type + " NULL" + })); + } } } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs new file mode 100644 index 0000000000..79fc893f4c --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteDisplayPreferencesRepository + /// + public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository + { + private readonly IMemoryStreamFactory _memoryStreamProvider; + + public SqliteDisplayPreferencesRepository(ILogger logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider) + : base(logger) + { + _jsonSerializer = jsonSerializer; + _memoryStreamProvider = memoryStreamProvider; + DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return "SQLite"; + } + } + + /// + /// The _json serializer + /// + private readonly IJsonSerializer _jsonSerializer; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists userdisplaypreferences (id GUID, userId GUID, client text, data BLOB)", + "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" + }; + + connection.RunQueries(queries); + } + } + + /// + /// Save the display preferences associated with an item in the repo + /// + /// The display preferences. + /// The user id. + /// The client. + /// The cancellation token. + /// Task. + /// item + public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException("displayPreferences"); + } + if (string.IsNullOrWhiteSpace(displayPreferences.Id)) + { + throw new ArgumentNullException("displayPreferences.Id"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + SaveDisplayPreferences(displayPreferences, userId, client, db); + }); + } + } + } + + private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) + { + var commandText = "replace into userdisplaypreferences (id, userid, client, data) values (?, ?, ?, ?)"; + var serialized = _jsonSerializer.SerializeToBytes(displayPreferences, _memoryStreamProvider); + + connection.Execute(commandText, + displayPreferences.Id.ToGuidParamValue(), + userId.ToGuidParamValue(), + client, + serialized); + } + + /// + /// Save all display preferences associated with a user in the repo + /// + /// The display preferences. + /// The user id. + /// The cancellation token. + /// Task. + /// item + public async Task SaveAllDisplayPreferences(IEnumerable displayPreferences, Guid userId, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException("displayPreferences"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + foreach (var displayPreference in displayPreferences) + { + SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); + } + }); + } + } + } + + /// + /// Gets the display preferences. + /// + /// The display preferences id. + /// The user id. + /// The client. + /// Task{DisplayPreferences}. + /// item + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) + { + if (string.IsNullOrWhiteSpace(displayPreferencesId)) + { + throw new ArgumentNullException("displayPreferencesId"); + } + + var guidId = displayPreferencesId.GetMD5(); + + using (var connection = CreateConnection(true)) + { + var commandText = "select data from userdisplaypreferences where id = ? and userId=? and client=?"; + + var paramList = new List(); + paramList.Add(guidId.ToGuidParamValue()); + paramList.Add(userId.ToGuidParamValue()); + paramList.Add(client); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return Get(row); + } + + return new DisplayPreferences + { + Id = guidId.ToString("N") + }; + } + } + + /// + /// Gets all display preferences for the given user. + /// + /// The user id. + /// Task{DisplayPreferences}. + /// item + public IEnumerable GetAllDisplayPreferences(Guid userId) + { + var list = new List(); + + using (var connection = CreateConnection(true)) + { + var commandText = "select data from userdisplaypreferences where userId=?"; + + var paramList = new List(); + paramList.Add(userId.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(Get(row)); + } + } + + return list; + } + + private DisplayPreferences Get(IReadOnlyList row) + { + using (var stream = _memoryStreamProvider.CreateNew(row[0].ToBlob())) + { + stream.Position = 0; + return _jsonSerializer.DeserializeFromStream(stream); + } + } + + public Task SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) + { + return SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); + } + + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) + { + return GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 62615c669a..d9536ae9c5 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data @@ -25,7 +27,12 @@ namespace Emby.Server.Implementations.Data public static byte[] ToGuidParamValue(this string str) { - return new Guid(str).ToByteArray(); + return ToGuidParamValue(new Guid(str)); + } + + public static byte[] ToGuidParamValue(this Guid guid) + { + return guid.ToByteArray(); } public static Guid ReadGuid(this IResultSetValue result) @@ -101,5 +108,24 @@ namespace Emby.Server.Implementations.Data DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); } + + /// + /// Serializes to bytes. + /// + /// System.Byte[][]. + /// obj + public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamFactory streamProvider) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + using (var stream = streamProvider.CreateNew()) + { + json.SerializeToStream(obj, stream); + return stream.ToArray(); + } + } } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 6843ad9d73..a4f26bc601 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -50,6 +50,7 @@ + @@ -192,6 +193,7 @@ + diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs new file mode 100644 index 0000000000..5179bd2581 --- /dev/null +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Security +{ + public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository + { + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + : base(logger) + { + _appPaths = appPaths; + DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db"); + } + + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, AppVersion TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)" + }; + + connection.RunQueries(queries); + + connection.RunInTransaction(db => + { + AddColumn(db, "AccessTokens", "AppVersion", "TEXT"); + }); + } + } + + 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(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + db.Execute(commandText, + info.Id.ToGuidParamValue(), + info.AccessToken, + info.DeviceId, + info.AppName, + info.AppVersion, + info.DeviceName, + info.UserId, + info.IsActive, + info.DateCreated.ToDateTimeParamValue(), + info.DateRevoked.HasValue ? info.DateRevoked.Value.ToDateTimeParamValue() : null); + }); + } + } + } + + private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var connection = CreateConnection(true)) + { + var commandText = BaseSelectText; + var paramList = new List(); + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=?"); + paramList.Add(query.AccessToken); + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=?"); + paramList.Add(query.UserId); + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=?"); + paramList.Add(query.DeviceId); + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=?"); + paramList.Add(query.IsActive.Value); + } + + if (query.HasUser.HasValue) + { + if (query.HasUser.Value) + { + whereClauses.Add("UserId not null"); + } + else + { + whereClauses.Add("UserId is null"); + } + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + if (startIndex > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", + pagingWhereText, + startIndex.ToString(_usCulture))); + } + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + commandText += whereText; + + commandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + var list = new List(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(Get(row)); + } + + var count = connection.Query("select count (Id) from AccessTokens" + whereTextWithoutPaging, paramList.ToArray()) + .SelectScalarInt() + .First(); + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = BaseSelectText + " where Id=?"; + var paramList = new List(); + + paramList.Add(id.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return Get(row); + } + return null; + } + } + } + + private AuthenticationInfo Get(IReadOnlyList reader) + { + var info = new AuthenticationInfo + { + Id = reader[0].ReadGuid().ToString("N"), + AccessToken = reader[1].ToString() + }; + + if (reader[2].SQLiteType != SQLiteType.Null) + { + info.DeviceId = reader[2].ToString(); + } + + if (reader[3].SQLiteType != SQLiteType.Null) + { + info.AppName = reader[3].ToString(); + } + + if (reader[4].SQLiteType != SQLiteType.Null) + { + info.AppVersion = reader[4].ToString(); + } + + if (reader[5].SQLiteType != SQLiteType.Null) + { + info.DeviceName = reader[5].ToString(); + } + + if (reader[6].SQLiteType != SQLiteType.Null) + { + info.UserId = reader[6].ToString(); + } + + info.IsActive = reader[7].ToBool(); + info.DateCreated = reader[8].ReadDateTime(); + + if (reader[9].SQLiteType != SQLiteType.Null) + { + info.DateRevoked = reader[9].ReadDateTime(); + } + + return info; + } + } +} -- cgit v1.2.3