AuthenticationRepository.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. using MediaBrowser.Controller;
  2. using MediaBrowser.Controller.Security;
  3. using MediaBrowser.Model.Logging;
  4. using MediaBrowser.Model.Querying;
  5. using MediaBrowser.Server.Implementations.Persistence;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Data;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. namespace MediaBrowser.Server.Implementations.Security
  14. {
  15. public class AuthenticationRepository : IAuthenticationRepository
  16. {
  17. private IDbConnection _connection;
  18. private readonly ILogger _logger;
  19. private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
  20. private readonly IServerApplicationPaths _appPaths;
  21. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  22. private IDbCommand _saveInfoCommand;
  23. public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths)
  24. {
  25. _logger = logger;
  26. _appPaths = appPaths;
  27. }
  28. public async Task Initialize()
  29. {
  30. var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db");
  31. _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
  32. string[] queries = {
  33. "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)",
  34. "create index if not exists idx_AccessTokens on AccessTokens(Id)",
  35. //pragmas
  36. "pragma temp_store = memory",
  37. "pragma shrink_memory"
  38. };
  39. _connection.RunQueries(queries, _logger);
  40. PrepareStatements();
  41. }
  42. private void PrepareStatements()
  43. {
  44. _saveInfoCommand = _connection.CreateCommand();
  45. _saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)";
  46. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id");
  47. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken");
  48. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId");
  49. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName");
  50. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName");
  51. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId");
  52. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive");
  53. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated");
  54. _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked");
  55. }
  56. public Task Create(AuthenticationInfo info, CancellationToken cancellationToken)
  57. {
  58. info.Id = Guid.NewGuid().ToString("N");
  59. return Update(info, cancellationToken);
  60. }
  61. public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken)
  62. {
  63. if (info == null)
  64. {
  65. throw new ArgumentNullException("info");
  66. }
  67. cancellationToken.ThrowIfCancellationRequested();
  68. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  69. IDbTransaction transaction = null;
  70. try
  71. {
  72. transaction = _connection.BeginTransaction();
  73. var index = 0;
  74. _saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id);
  75. _saveInfoCommand.GetParameter(index++).Value = info.AccessToken;
  76. _saveInfoCommand.GetParameter(index++).Value = info.DeviceId;
  77. _saveInfoCommand.GetParameter(index++).Value = info.AppName;
  78. _saveInfoCommand.GetParameter(index++).Value = info.DeviceName;
  79. _saveInfoCommand.GetParameter(index++).Value = info.UserId;
  80. _saveInfoCommand.GetParameter(index++).Value = info.IsActive;
  81. _saveInfoCommand.GetParameter(index++).Value = info.DateCreated;
  82. _saveInfoCommand.GetParameter(index++).Value = info.DateRevoked;
  83. _saveInfoCommand.Transaction = transaction;
  84. _saveInfoCommand.ExecuteNonQuery();
  85. transaction.Commit();
  86. }
  87. catch (OperationCanceledException)
  88. {
  89. if (transaction != null)
  90. {
  91. transaction.Rollback();
  92. }
  93. throw;
  94. }
  95. catch (Exception e)
  96. {
  97. _logger.ErrorException("Failed to save record:", e);
  98. if (transaction != null)
  99. {
  100. transaction.Rollback();
  101. }
  102. throw;
  103. }
  104. finally
  105. {
  106. if (transaction != null)
  107. {
  108. transaction.Dispose();
  109. }
  110. _writeLock.Release();
  111. }
  112. }
  113. private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens";
  114. public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query)
  115. {
  116. if (query == null)
  117. {
  118. throw new ArgumentNullException("query");
  119. }
  120. using (var cmd = _connection.CreateCommand())
  121. {
  122. cmd.CommandText = BaseSelectText;
  123. var whereClauses = new List<string>();
  124. var startIndex = query.StartIndex ?? 0;
  125. if (startIndex > 0)
  126. {
  127. whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens ORDER BY DateCreated LIMIT {0})",
  128. startIndex.ToString(_usCulture)));
  129. }
  130. if (!string.IsNullOrWhiteSpace(query.AccessToken))
  131. {
  132. whereClauses.Add("AccessToken=@AccessToken");
  133. cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken;
  134. }
  135. if (!string.IsNullOrWhiteSpace(query.UserId))
  136. {
  137. whereClauses.Add("UserId=@UserId");
  138. cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
  139. }
  140. if (!string.IsNullOrWhiteSpace(query.DeviceId))
  141. {
  142. whereClauses.Add("DeviceId=@DeviceId");
  143. cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId;
  144. }
  145. if (query.IsActive.HasValue)
  146. {
  147. whereClauses.Add("IsActive=@IsActive");
  148. cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value;
  149. }
  150. if (whereClauses.Count > 0)
  151. {
  152. cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
  153. }
  154. cmd.CommandText += " ORDER BY DateCreated";
  155. if (query.Limit.HasValue)
  156. {
  157. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
  158. }
  159. cmd.CommandText += "; select count (Id) from AccessTokens";
  160. var list = new List<AuthenticationInfo>();
  161. var count = 0;
  162. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  163. {
  164. while (reader.Read())
  165. {
  166. list.Add(Get(reader));
  167. }
  168. if (reader.NextResult() && reader.Read())
  169. {
  170. count = reader.GetInt32(0);
  171. }
  172. }
  173. return new QueryResult<AuthenticationInfo>()
  174. {
  175. Items = list.ToArray(),
  176. TotalRecordCount = count
  177. };
  178. }
  179. }
  180. public AuthenticationInfo Get(string id)
  181. {
  182. if (string.IsNullOrEmpty(id))
  183. {
  184. throw new ArgumentNullException("id");
  185. }
  186. var guid = new Guid(id);
  187. using (var cmd = _connection.CreateCommand())
  188. {
  189. cmd.CommandText = BaseSelectText + " where Id=@Id";
  190. cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
  191. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
  192. {
  193. if (reader.Read())
  194. {
  195. return Get(reader);
  196. }
  197. }
  198. }
  199. return null;
  200. }
  201. private AuthenticationInfo Get(IDataReader reader)
  202. {
  203. var s = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens";
  204. var info = new AuthenticationInfo
  205. {
  206. Id = reader.GetGuid(0).ToString("N"),
  207. AccessToken = reader.GetString(1)
  208. };
  209. if (!reader.IsDBNull(2))
  210. {
  211. info.DeviceId = reader.GetString(2);
  212. }
  213. if (!reader.IsDBNull(3))
  214. {
  215. info.AppName = reader.GetString(3);
  216. }
  217. if (!reader.IsDBNull(4))
  218. {
  219. info.DeviceName = reader.GetString(4);
  220. }
  221. if (!reader.IsDBNull(5))
  222. {
  223. info.UserId = reader.GetString(5);
  224. }
  225. info.IsActive = reader.GetBoolean(6);
  226. info.DateCreated = reader.GetDateTime(7).ToUniversalTime();
  227. if (!reader.IsDBNull(8))
  228. {
  229. info.DateRevoked = reader.GetDateTime(8).ToUniversalTime();
  230. }
  231. return info;
  232. }
  233. /// <summary>
  234. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  235. /// </summary>
  236. public void Dispose()
  237. {
  238. Dispose(true);
  239. GC.SuppressFinalize(this);
  240. }
  241. private readonly object _disposeLock = new object();
  242. /// <summary>
  243. /// Releases unmanaged and - optionally - managed resources.
  244. /// </summary>
  245. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  246. protected virtual void Dispose(bool dispose)
  247. {
  248. if (dispose)
  249. {
  250. try
  251. {
  252. lock (_disposeLock)
  253. {
  254. if (_connection != null)
  255. {
  256. if (_connection.IsOpen())
  257. {
  258. _connection.Close();
  259. }
  260. _connection.Dispose();
  261. _connection = null;
  262. }
  263. }
  264. }
  265. catch (Exception ex)
  266. {
  267. _logger.ErrorException("Error disposing database", ex);
  268. }
  269. }
  270. }
  271. }
  272. }