diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 8163ca006..914034f74 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -18,6 +18,8 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Repos = Bit.Core.Repositories.SqlServer; using System.Text; +using StackExchange.Redis.Extensions.Core; +using StackExchange.Redis.Extensions.Protobuf; //using Loggr.Extensions.Logging; namespace Bit.Api @@ -55,9 +57,16 @@ namespace Bit.Api ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), globalSettings); services.AddSingleton(s => globalSettings); + // Caching + ISerializer serializer = new ProtobufSerializer(); + services.AddSingleton(s => serializer); + ICacheClient cacheClient = new StackExchangeRedisCacheClient(serializer, + globalSettings.Cache.ConnectionString, globalSettings.Cache.Database); + services.AddSingleton(s => cacheClient); + // Repositories - services.AddSingleton(s => new Repos.UserRepository(globalSettings.SqlServer.ConnectionString)); - services.AddSingleton(s => new Repos.CipherRepository(globalSettings.SqlServer.ConnectionString)); + services.AddSingleton(); + services.AddSingleton(); // Context services.AddScoped(); diff --git a/src/Api/settings.json b/src/Api/settings.json index ca0aebe43..af8f7fdb1 100644 --- a/src/Api/settings.json +++ b/src/Api/settings.json @@ -1,18 +1,22 @@ { - "globalSettings": { - "siteName": "bitwarden", - "baseVaultUri": "http://localhost:4001", - "jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)", - "sqlServer": { - "connectionString": "SECRET" - }, - "mail": { - "apiKey": "SECRET", - "replyToEmail": "do-not-reply@bitwarden.com" - }, - "loggr": { - "logKey": "SECRET", - "apiKey": "SECRET" - } + "globalSettings": { + "siteName": "bitwarden", + "baseVaultUri": "http://localhost:4001", + "jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)", + "sqlServer": { + "connectionString": "SECRET" + }, + "mail": { + "apiKey": "SECRET", + "replyToEmail": "do-not-reply@bitwarden.com" + }, + "loggr": { + "logKey": "SECRET", + "apiKey": "SECRET" + }, + "cache": { + "connectionString": "SECRET.COM:6380,password=SECRET,ssl=True,abortConnect=False", + "database": 0 } + } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs new file mode 100644 index 000000000..95125e258 --- /dev/null +++ b/src/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace Bit.Core +{ + public static class Constants + { + public const string UserIdCacheKey = "User:Id_{0}"; + } +} diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index d859d0c7b..1785e0f0e 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -1,4 +1,5 @@ -using System; +using StackExchange.Redis.Extensions.Core.Configuration; +using System; using System.Collections.Generic; namespace Bit.Core @@ -11,6 +12,7 @@ namespace Bit.Core public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual LoggrSettings Loggr { get; set; } = new LoggrSettings(); + public virtual CacheSettings Cache { get; set; } = new CacheSettings(); public class SqlServerSettings { @@ -28,5 +30,11 @@ namespace Bit.Core public string LogKey { get; set; } public string ApiKey { get; set; } } + + public class CacheSettings + { + public string ConnectionString { get; set; } + public int Database { get; set; } + } } } diff --git a/src/Core/Repositories/IRepository.cs b/src/Core/Repositories/IRepository.cs index 71d0f15a5..d577f35c3 100644 --- a/src/Core/Repositories/IRepository.cs +++ b/src/Core/Repositories/IRepository.cs @@ -9,7 +9,6 @@ namespace Bit.Core.Repositories Task CreateAsync(T obj); Task ReplaceAsync(T obj); Task UpsertAsync(T obj); - Task DeleteByIdAsync(TId id); Task DeleteAsync(T obj); } } diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index ad91274da..ed7201d79 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -7,15 +7,24 @@ using DataTableProxy; using Bit.Core.Domains; using System.Data; using Dapper; +using StackExchange.Redis.Extensions.Core; namespace Bit.Core.Repositories.SqlServer { public class CipherRepository : Repository, ICipherRepository { - public CipherRepository(string connectionString) - : base(connectionString) + private readonly ICacheClient _cacheClient; + + public CipherRepository(GlobalSettings globalSettings, ICacheClient cacheClient) + : this(globalSettings.SqlServer.ConnectionString, cacheClient) { } + public CipherRepository(string connectionString, ICacheClient cacheClient) + : base(connectionString) + { + _cacheClient = cacheClient; + } + public async Task GetByIdAsync(Guid id, Guid userId) { var cipher = await GetByIdAsync(id); @@ -57,7 +66,7 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task, ICollection>> + public async Task, ICollection>> GetManySinceRevisionDateAndUserIdWithDeleteHistoryAsync(DateTime sinceRevisionDate, Guid userId) { using(var connection = new SqlConnection(ConnectionString)) @@ -78,11 +87,11 @@ namespace Bit.Core.Repositories.SqlServer } } - public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers) + public async Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers) { if(ciphers.Count() == 0) { - return Task.FromResult(0); + return; } using(var connection = new SqlConnection(ConnectionString)) @@ -167,7 +176,8 @@ namespace Bit.Core.Repositories.SqlServer } } - return Task.FromResult(0); + // Cleanup user cache + await _cacheClient.RemoveAllAsync(new string[] { string.Format(Constants.UserIdCacheKey, user.Id) }); } public Task CreateAsync(IEnumerable ciphers) diff --git a/src/Core/Repositories/SqlServer/Repository.cs b/src/Core/Repositories/SqlServer/Repository.cs index d807b395a..6352e4fad 100644 --- a/src/Core/Repositories/SqlServer/Repository.cs +++ b/src/Core/Repositories/SqlServer/Repository.cs @@ -75,17 +75,12 @@ namespace Bit.Core.Repositories.SqlServer } public virtual async Task DeleteAsync(T obj) - { - await DeleteByIdAsync(obj.Id); - } - - public virtual async Task DeleteByIdAsync(TId id) { using(var connection = new SqlConnection(ConnectionString)) { await connection.ExecuteAsync( $"[{Schema}].[{Table}_DeleteById]", - new { Id = id }, + new { Id = obj.Id }, commandType: CommandType.StoredProcedure); } } diff --git a/src/Core/Repositories/SqlServer/UserRepository.cs b/src/Core/Repositories/SqlServer/UserRepository.cs index 5d9fda57c..771c7ac06 100644 --- a/src/Core/Repositories/SqlServer/UserRepository.cs +++ b/src/Core/Repositories/SqlServer/UserRepository.cs @@ -5,15 +5,39 @@ using System.Linq; using System.Threading.Tasks; using Bit.Core.Domains; using Dapper; +using StackExchange.Redis.Extensions.Core; namespace Bit.Core.Repositories.SqlServer { public class UserRepository : Repository, IUserRepository { - public UserRepository(string connectionString) - : base(connectionString) + private readonly ICacheClient _cacheClient; + + public UserRepository(GlobalSettings globalSettings, ICacheClient cacheClient) + : this(globalSettings.SqlServer.ConnectionString, cacheClient) { } + public UserRepository(string connectionString, ICacheClient cacheClient) + : base(connectionString) + { + _cacheClient = cacheClient; + } + + public override async Task GetByIdAsync(Guid id) + { + var cacheKey = string.Format(Constants.UserIdCacheKey, id); + + var user = await _cacheClient.GetAsync(cacheKey); + if(user != null) + { + return user; + } + + user = await base.GetByIdAsync(id); + await _cacheClient.AddAsync(cacheKey, user); + return user; + } + public async Task GetByEmailAsync(string email) { using(var connection = new SqlConnection(ConnectionString)) @@ -26,5 +50,22 @@ namespace Bit.Core.Repositories.SqlServer return results.SingleOrDefault(); } } + + public override async Task ReplaceAsync(User user) + { + await base.ReplaceAsync(user); + await PurgeCacheAsync(user); + } + + public override async Task DeleteAsync(User user) + { + await base.DeleteAsync(user); + await PurgeCacheAsync(user); + } + + private async Task PurgeCacheAsync(User user) + { + await _cacheClient.RemoveAllAsync(new string[] { string.Format(Constants.UserIdCacheKey, user.Id) }); + } } } diff --git a/src/Core/project.json b/src/Core/project.json index 14b700438..ea0323c22 100644 --- a/src/Core/project.json +++ b/src/Core/project.json @@ -10,7 +10,9 @@ "Microsoft.AspNetCore.Mvc.Abstractions": "1.0.0-rc2-final", "Dapper": "1.42.0", "DataTableProxy": "1.2.0", - "Sendgrid": "6.3.4" + "Sendgrid": "6.3.4", + "StackExchange.Redis": "1.0.488", + "StackExchange.Redis.Extensions.Protobuf": "1.3.5" }, "frameworks": {