diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index a9a50e7bc..4d04db1d7 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Microsoft.AspNet.Authorization; -using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Mvc; using Bit.Api.Models; using Bit.Core.Exceptions; @@ -36,18 +35,11 @@ namespace Bit.Api.Controllers _currentContext = currentContext; } - [HttpPost("register-token")] - [AllowAnonymous] - public async Task PostRegisterToken([FromBody]RegisterTokenRequestModel model) - { - await _userService.InitiateRegistrationAsync(model.Email); - } - [HttpPost("register")] [AllowAnonymous] public async Task PostRegister([FromBody]RegisterRequestModel model) { - var result = await _userService.RegisterUserAsync(model.Token, model.ToUser(), model.MasterPasswordHash); + var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash); if(result.Succeeded) { return; diff --git a/src/Api/Models/Request/Accounts/RegisterRequestModel.cs b/src/Api/Models/Request/Accounts/RegisterRequestModel.cs index be1efa2bd..b477b7dfb 100644 --- a/src/Api/Models/Request/Accounts/RegisterRequestModel.cs +++ b/src/Api/Models/Request/Accounts/RegisterRequestModel.cs @@ -5,8 +5,6 @@ namespace Bit.Api.Models { public class RegisterRequestModel { - [Required] - public string Token { get; set; } [Required] [StringLength(50)] public string Name { get; set; } diff --git a/src/Api/Models/Request/Accounts/RegisterTokenRequestModel.cs b/src/Api/Models/Request/Accounts/RegisterTokenRequestModel.cs deleted file mode 100644 index 15b43d095..000000000 --- a/src/Api/Models/Request/Accounts/RegisterTokenRequestModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Api.Models -{ - public class RegisterTokenRequestModel - { - [Required] - [EmailAddress] - [StringLength(50)] - public string Email { get; set; } - } -} diff --git a/src/Api/project.json b/src/Api/project.json index 44f4271f2..dd8f2268d 100644 --- a/src/Api/project.json +++ b/src/Api/project.json @@ -10,7 +10,6 @@ "version": "0.0.1", "target": "project" }, - "Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc1-final", "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", diff --git a/src/Core/Domains/Cipher.cs b/src/Core/Domains/Cipher.cs index 87acabb06..36d1a1a34 100644 --- a/src/Core/Domains/Cipher.cs +++ b/src/Core/Domains/Cipher.cs @@ -1,22 +1,12 @@ using System; -using Newtonsoft.Json; -using Bit.Core.Enums; namespace Bit.Core.Domains { public abstract class Cipher : IDataObject { - internal static string TypeValue = "cipher"; - - [JsonProperty("id")] public string Id { get; set; } - [JsonProperty("type")] - public string Type { get; private set; } = TypeValue; - public abstract CipherType CipherType { get; protected set; } - public string UserId { get; set; } public string Name { get; set; } - public bool Dirty { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; } diff --git a/src/Core/Domains/Folder.cs b/src/Core/Domains/Folder.cs index 1474a7bc2..65e9d1d6c 100644 --- a/src/Core/Domains/Folder.cs +++ b/src/Core/Domains/Folder.cs @@ -1,9 +1,6 @@ -using Bit.Core.Enums; - -namespace Bit.Core.Domains +namespace Bit.Core.Domains { public class Folder : Cipher, IDataObject { - public override CipherType CipherType { get; protected set; } = CipherType.Folder; } } diff --git a/src/Core/Domains/Site.cs b/src/Core/Domains/Site.cs index c227e6bb2..f3b7373d0 100644 --- a/src/Core/Domains/Site.cs +++ b/src/Core/Domains/Site.cs @@ -4,10 +4,7 @@ namespace Bit.Core.Domains { public class Site : Cipher, IDataObject { - public override CipherType CipherType { get; protected set; } = CipherType.Site; - public string FolderId { get; set; } - public string Uri { get; set; } public string Username { get; set; } public string Password { get; set; } diff --git a/src/Core/Domains/User.cs b/src/Core/Domains/User.cs index 6c5bf6b7a..eb2b557f4 100644 --- a/src/Core/Domains/User.cs +++ b/src/Core/Domains/User.cs @@ -1,30 +1,21 @@ using System; -using Newtonsoft.Json; using Bit.Core.Enums; namespace Bit.Core.Domains { public class User : IDataObject { - internal static string TypeValue = "user"; - - [JsonProperty("id")] public string Id { get; set; } - [JsonProperty("type")] - public string Type { get; private set; } = TypeValue; - public string Name { get; set; } public string Email { get; set; } public string MasterPassword { get; set; } public string MasterPasswordHint { get; set; } public string Culture { get; set; } = "en-US"; public string SecurityStamp { get; set; } - public string OldEmail { get; set; } - public string OldMasterPassword { get; set; } public bool TwoFactorEnabled { get; set; } public TwoFactorProvider? TwoFactorProvider { get; set; } public string AuthenticatorKey { get; set; } - public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; + public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; } } diff --git a/src/Core/IDataObject.cs b/src/Core/IDataObject.cs index 2ec0c6bb9..dc32a1f1e 100644 --- a/src/Core/IDataObject.cs +++ b/src/Core/IDataObject.cs @@ -1,12 +1,7 @@ -using Newtonsoft.Json; - -namespace Bit.Core +namespace Bit.Core { public interface IDataObject { - [JsonProperty("id")] string Id { get; set; } - [JsonProperty("type")] - string Type { get; } } } diff --git a/src/Core/Repositories/DocumentDB/BaseRepository.cs b/src/Core/Repositories/DocumentDB/BaseRepository.cs deleted file mode 100644 index 794c0c252..000000000 --- a/src/Core/Repositories/DocumentDB/BaseRepository.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.Azure.Documents.Client; - -namespace Bit.Core.Repositories.DocumentDB -{ - public abstract class BaseRepository where T : IDataObject - { - public BaseRepository(DocumentClient client, string databaseId, string documentType = null) - { - Client = client; - DatabaseId = databaseId; - DatabaseUri = UriFactory.CreateDatabaseUri(databaseId); - PartitionResolver = client.PartitionResolvers[DatabaseUri.OriginalString]; - - if(string.IsNullOrWhiteSpace(documentType)) - { - DocumentType = typeof(T).Name.ToLower(); - } - else - { - DocumentType = documentType; - } - } - - protected DocumentClient Client { get; private set; } - protected string DatabaseId { get; private set; } - protected Uri DatabaseUri { get; private set; } - protected IPartitionResolver PartitionResolver { get; private set; } - protected string DocumentType { get; private set; } - - protected string ResolveSprocIdLink(T obj, string sprocId) - { - return string.Format("{0}/sprocs/{1}", ResolveCollectionIdLink(obj), sprocId); - } - - protected string ResolveSprocIdLink(string partitionKey, string sprocId) - { - return string.Format("{0}/sprocs/{1}", ResolveCollectionIdLink(partitionKey), sprocId); - } - - protected string ResolveDocumentIdLink(T obj) - { - return string.Format("{0}/docs/{1}", ResolveCollectionIdLink(obj), obj.Id); - } - - protected string ResolveDocumentIdLink(string id) - { - return ResolveDocumentIdLink(id, id); - } - - protected string ResolveDocumentIdLink(string partitionKey, string id) - { - return string.Format("{0}/docs/{1}", ResolveCollectionIdLink(partitionKey), id); - } - - protected string ResolveCollectionIdLink(T obj) - { - var partitionKey = PartitionResolver.GetPartitionKey(obj); - return ResolveCollectionIdLink(partitionKey); - } - - protected string ResolveCollectionIdLink(object partitionKey) - { - return PartitionResolver.ResolveForCreate(partitionKey); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/CipherRepository.cs b/src/Core/Repositories/DocumentDB/CipherRepository.cs deleted file mode 100644 index cd698a759..000000000 --- a/src/Core/Repositories/DocumentDB/CipherRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; -using Bit.Core.Domains; -using Bit.Core.Repositories.DocumentDB.Utilities; - -namespace Bit.Core.Repositories.DocumentDB -{ - public class CipherRepository : BaseRepository, ICipherRepository - { - public CipherRepository(DocumentClient client, string databaseId, string documentType = null) - : base(client, databaseId, documentType) - { } - - public async Task DirtyCiphersAsync(string userId) - { - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - while(true) - { - StoredProcedureResponse sprocResponse = await Client.ExecuteStoredProcedureAsync( - ResolveSprocIdLink(userId, "dirtyCiphers"), - userId); - - if(!(bool)sprocResponse.Response.continuation) - { - break; - } - } - }); - } - - public async Task UpdateDirtyCiphersAsync(IEnumerable ciphers) - { - // Make sure we are dealing with cipher types since we accept any via dynamic. - var cleanedCiphers = ciphers.Where(c => c is Cipher); - if(cleanedCiphers.Count() == 0) - { - return; - } - - var takeCount = DocumentDBHelpers.GetTakeCount(ciphers, 500); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - var userId = ((Cipher)cleanedCiphers.First()).UserId; - StoredProcedureResponse sprocResponse = await Client.ExecuteStoredProcedureAsync( - ResolveSprocIdLink(userId, "updateDirtyCiphers"), - cleanedCiphers.Take(takeCount), - userId); - - var replacedCount = sprocResponse.Response; - if(replacedCount != cleanedCiphers.Count()) - { - await UpdateDirtyCiphersAsync(cleanedCiphers.Skip(replacedCount)); - } - }); - } - - public async Task CreateAsync(IEnumerable ciphers) - { - // Make sure we are dealing with cipher types since we accept any via dynamic. - var cleanedCiphers = ciphers.Where(c => c is Cipher); - if(cleanedCiphers.Count() == 0) - { - return; - } - - var takeCount = DocumentDBHelpers.GetTakeCount(ciphers, 500); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - var userId = ((Cipher)cleanedCiphers.First()).UserId; - StoredProcedureResponse sprocResponse = await Client.ExecuteStoredProcedureAsync( - ResolveSprocIdLink(userId, "bulkCreate"), - cleanedCiphers.Take(takeCount)); - - var createdCount = sprocResponse.Response; - if(createdCount != cleanedCiphers.Count()) - { - await CreateAsync(cleanedCiphers.Skip(createdCount)); - } - }); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/FolderRepository.cs b/src/Core/Repositories/DocumentDB/FolderRepository.cs deleted file mode 100644 index 6289823ff..000000000 --- a/src/Core/Repositories/DocumentDB/FolderRepository.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; -using Bit.Core.Domains; -using Bit.Core.Enums; -using Bit.Core.Repositories.DocumentDB.Utilities; -using Microsoft.Azure.Documents; - -namespace Bit.Core.Repositories.DocumentDB -{ - public class FolderRepository : Repository, IFolderRepository - { - public FolderRepository(DocumentClient client, string databaseId) - : base(client, databaseId) - { } - - public async Task GetByIdAsync(string id, string userId) - { - ResourceResponse doc = null; - var docLink = ResolveDocumentIdLink(userId, id); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - doc = await Client.ReadDocumentAsync(docLink); - }); - - if(doc?.Resource == null) - { - return null; - } - - var folder = (Folder)((dynamic)doc.Resource); - if(folder.UserId != userId) - { - return null; - } - - return folder; - } - - public async Task> GetManyByUserIdAsync(string userId) - { - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, null, userId) - .Where(d => d.Type == Cipher.TypeValue && d.CipherType == CipherType.Folder && d.UserId == userId).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.ToList(); - } - - public async Task> GetManyByUserIdAsync(string userId, bool dirty) - { - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, null, userId) - .Where(d => d.Type == Cipher.TypeValue && d.CipherType == CipherType.Folder && d.UserId == userId && d.Dirty == dirty).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.ToList(); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Repository.cs b/src/Core/Repositories/DocumentDB/Repository.cs deleted file mode 100644 index 834d8629d..000000000 --- a/src/Core/Repositories/DocumentDB/Repository.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.Core.Repositories.DocumentDB.Utilities; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace Bit.Core.Repositories.DocumentDB -{ - public abstract class Repository : BaseRepository, IRepository where T : IDataObject - { - public Repository(DocumentClient client, string databaseId, string documentType = null) - : base(client, databaseId, documentType) - { } - - public virtual async Task GetByIdAsync(string id) - { - // NOTE: Not an ideal condition, scanning all collections. - // Override this method if you can implement a direct partition lookup based on the id. - // Use the inherited GetByPartitionIdAsync method to implement your override. - - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, new FeedOptions { MaxItemCount = 1 }) - .Where(d => d.Id == id).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.FirstOrDefault(); - } - - public virtual async Task CreateAsync(T obj) - { - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - var result = await Client.CreateDocumentAsync(DatabaseUri, obj); - obj.Id = result.Resource.Id; - }); - } - - public virtual async Task ReplaceAsync(T obj) - { - var docLink = ResolveDocumentIdLink(obj); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - await Client.ReplaceDocumentAsync(docLink, obj); - }); - } - - public virtual async Task UpsertAsync(T obj) - { - var docLink = ResolveDocumentIdLink(obj); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - await Client.UpsertDocumentAsync(docLink, obj); - }); - } - - public virtual async Task DeleteAsync(T obj) - { - var docLink = ResolveDocumentIdLink(obj); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - await Client.DeleteDocumentAsync(docLink); - }); - } - - public virtual async Task DeleteByIdAsync(string id) - { - // NOTE: Not an ideal condition, scanning all collections. - // Override this method if you can implement a direct partition lookup based on the id. - // Use the inherited DeleteByPartitionIdAsync method to implement your override. - - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, new FeedOptions { MaxItemCount = 1 }) - .Where(d => d.Id == id).AsEnumerable(); - - return Task.FromResult(0); - }); - - if(docs != null && docs.Count() > 0) - { - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - await Client.DeleteDocumentAsync(docs.First().SelfLink); - }); - } - } - - protected async Task GetByPartitionIdAsync(string id) - { - ResourceResponse doc = null; - var docLink = ResolveDocumentIdLink(id); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - doc = await Client.ReadDocumentAsync(docLink); - }); - - if(doc?.Resource == null) - { - return default(T); - } - - return (T)((dynamic)doc.Resource); - } - - protected async Task DeleteByPartitionIdAsync(string id) - { - var docLink = ResolveDocumentIdLink(id); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - await Client.DeleteDocumentAsync(docLink); - }); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/SiteRepository.cs b/src/Core/Repositories/DocumentDB/SiteRepository.cs deleted file mode 100644 index 7a4756ef0..000000000 --- a/src/Core/Repositories/DocumentDB/SiteRepository.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; -using Bit.Core.Domains; -using Bit.Core.Enums; -using Bit.Core.Repositories.DocumentDB.Utilities; -using Microsoft.Azure.Documents; - -namespace Bit.Core.Repositories.DocumentDB -{ - public class SiteRepository : Repository, ISiteRepository - { - public SiteRepository(DocumentClient client, string databaseId) - : base(client, databaseId) - { } - - public async Task GetByIdAsync(string id, string userId) - { - ResourceResponse doc = null; - var docLink = ResolveDocumentIdLink(userId, id); - - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - doc = await Client.ReadDocumentAsync(docLink); - }); - - if(doc?.Resource == null) - { - return null; - } - - var site = (Site)((dynamic)doc.Resource); - if(site.UserId != userId) - { - return null; - } - - return site; - } - - public async Task> GetManyByUserIdAsync(string userId) - { - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, null, userId) - .Where(d => d.Type == Cipher.TypeValue && d.CipherType == CipherType.Site && d.UserId == userId).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.ToList(); - } - - public async Task> GetManyByUserIdAsync(string userId, bool dirty) - { - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, null, userId) - .Where(d => d.Type == Cipher.TypeValue && d.CipherType == CipherType.Site && d.UserId == userId && d.Dirty == dirty).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.ToList(); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Stored Procedures/bulkCreate.js b/src/Core/Repositories/DocumentDB/Stored Procedures/bulkCreate.js deleted file mode 100644 index d947a154d..000000000 --- a/src/Core/Repositories/DocumentDB/Stored Procedures/bulkCreate.js +++ /dev/null @@ -1,60 +0,0 @@ -/** -* This script called as stored procedure to import lots of documents in one batch. -* The script sets response body to the number of docs imported and is called multiple times -* by the client until total number of docs desired by the client is imported. -* @param {Object[]} docs - Array of documents to import. -*/ - -function bulkCreate(docs) { - var collection = getContext().getCollection(); - var collectionLink = collection.getSelfLink(); - - // The count of imported docs, also used as current doc index. - var count = 0; - - // Validate input. - if (!docs) throw new Error('The array is undefined or null.'); - - var docsLength = docs.length; - if (docsLength == 0) { - getContext().getResponse().setBody(0); - return; - } - - // Call the CRUD API to create a document. - tryCreate(docs[count], callback); - - // Note that there are 2 exit conditions: - // 1) The createDocument request was not accepted. - // In this case the callback will not be called, we just call setBody and we are done. - // 2) The callback was called docs.length times. - // In this case all documents were created and we don't need to call tryCreate anymore. Just call setBody and we are done. - function tryCreate(doc, callback) { - var isAccepted = collection.createDocument(collectionLink, doc, callback); - - // If the request was accepted, callback will be called. - // Otherwise report current count back to the client, - // which will call the script again with remaining set of docs. - // This condition will happen when this stored procedure has been running too long - // and is about to get cancelled by the server. This will allow the calling client - // to resume this batch from the point we got to before isAccepted was set to false - if (!isAccepted) getContext().getResponse().setBody(count); - } - - // This is called when collection.createDocument is done and the document has been persisted. - function callback(err, doc, options) { - if (err) throw err; - - // One more document has been inserted, increment the count. - count++; - - if (count >= docsLength) { - // If we have created all documents, we are done. Just set the response. - getContext().getResponse().setBody(count); - } - else { - // Create next document. - tryCreate(docs[count], callback); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Stored Procedures/bulkDelete.js b/src/Core/Repositories/DocumentDB/Stored Procedures/bulkDelete.js deleted file mode 100644 index f7fd97b28..000000000 --- a/src/Core/Repositories/DocumentDB/Stored Procedures/bulkDelete.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * A DocumentDB stored procedure that bulk deletes documents for a given query.
- * Note: You may need to execute this sproc multiple times (depending whether the sproc is able to delete every document within the execution timeout limit). - * - * @function - * @param {string} query - A query that provides the documents to be deleted (e.g. "SELECT * FROM c WHERE c.founded_year = 2008") - * @returns {Object.} Returns an object with the two properties:
- * deleted - contains a count of documents deleted
- * continuation - a boolean whether you should execute the sproc again (true if there are more documents to delete; false otherwise). - */ - -function bulkDelete(query) { - var collection = getContext().getCollection(); - var collectionLink = collection.getSelfLink(); - var response = getContext().getResponse(); - var responseBody = { - deleted: 0, - continuation: true - }; - - // Validate input. - if (!query) throw new Error('The query is undefined or null.'); - - tryQueryAndDelete(); - - // Recursively runs the query w/ support for continuation tokens. - // Calls tryDelete(documents) as soon as the query returns documents. - function tryQueryAndDelete(continuation) { - var requestOptions = { continuation: continuation }; - - var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, retrievedDocs, responseOptions) { - if (err) throw err; - - if (retrievedDocs.length > 0) { - // Begin deleting documents as soon as documents are returned form the query results. - // tryDelete() resumes querying after deleting; no need to page through continuation tokens. - // - this is to prioritize writes over reads given timeout constraints. - tryDelete(retrievedDocs); - } - else if (responseOptions.continuation) { - // Else if the query came back empty, but with a continuation token; repeat the query w/ the token. - tryQueryAndDelete(responseOptions.continuation); - } - else { - // Else if there are no more documents and no continuation token - we are finished deleting documents. - responseBody.continuation = false; - response.setBody(responseBody); - } - }); - - // If we hit execution bounds - return continuation: true. - if (!isAccepted) { - response.setBody(responseBody); - } - } - - // Recursively deletes documents passed in as an array argument. - // Attempts to query for more on empty array. - function tryDelete(documents) { - if (documents.length > 0) { - // Delete the first document in the array. - var isAccepted = collection.deleteDocument(documents[0]._self, {}, function (err, responseOptions) { - if (err) throw err; - - responseBody.deleted++; - documents.shift(); - // Delete the next document in the array. - tryDelete(documents); - }); - - // If we hit execution bounds - return continuation: true. - if (!isAccepted) { - response.setBody(responseBody); - } - } - else { - // If the document array is empty, query for more documents. - tryQueryAndDelete(); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Stored Procedures/dirtyCiphers.js b/src/Core/Repositories/DocumentDB/Stored Procedures/dirtyCiphers.js deleted file mode 100644 index 2c14370f8..000000000 --- a/src/Core/Repositories/DocumentDB/Stored Procedures/dirtyCiphers.js +++ /dev/null @@ -1,65 +0,0 @@ -// Update all ciphers for a user to be dirty - -function dirtyCiphers(userId) { - var collection = getContext().getCollection(); - var collectionLink = collection.getSelfLink(); - var response = getContext().getResponse(); - var responseBody = { - updated: 0, - continuation: true - }; - - if (!userId) throw new Error('The userId is undefined or null.'); - - tryQueryAndUpdate(); - - function tryQueryAndUpdate(continuation) { - var query = { - query: "SELECT * FROM root r WHERE r.UserId = @userId AND r.type = 'cipher' AND r.Dirty = false", - parameters: [{ name: '@userId', value: userId }] - }; - - var requestOptions = { continuation: continuation }; - var accepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, retrievedDocs, responseOptions) { - if (err) throw err; - - if (retrievedDocs.length > 0) { - tryUpdate(retrievedDocs); - } - else if (responseOptions.continuation) { - tryQueryAndUpdate(responseOptions.continuation); - } - else { - responseBody.continuation = false; - response.setBody(responseBody); - } - }); - - if (!accepted) { - response.setBody(responseBody); - } - } - - function tryUpdate(documents) { - if (documents.length > 0) { - // dirty it - documents[0].Dirty = true; - - var accepted = collection.replaceDocument(documents[0]._self, documents[0], {}, function (err, replacedDoc) { - if (err) throw err; - - responseBody.updated++; - documents.shift(); - - tryUpdate(documents); - }); - - if (!accepted) { - response.setBody(responseBody); - } - } - else { - tryQueryAndUpdate(); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Stored Procedures/updateDirtyCiphers.js b/src/Core/Repositories/DocumentDB/Stored Procedures/updateDirtyCiphers.js deleted file mode 100644 index 0f6e4378d..000000000 --- a/src/Core/Repositories/DocumentDB/Stored Procedures/updateDirtyCiphers.js +++ /dev/null @@ -1,87 +0,0 @@ -// Update an array of dirty ciphers for a user. - -function updateDirtyCiphers(ciphers, userId) { - var context = getContext(); - var collection = context.getCollection(); - var collectionLink = collection.getSelfLink(); - var response = context.getResponse(); - - var count = 0; - - // Validate input. - if (!ciphers) { - throw new Error('The ciphers array is undefined or null.'); - } - - var ciphersLength = ciphers.length; - if (ciphersLength == 0) { - response.setBody(0); - return; - } - - queryAndReplace(ciphers[count]); - - function queryAndReplace(cipher, continuation) { - var query = { - query: "SELECT * FROM root r WHERE r.id = @id AND r.UserId = @userId AND r.type = 'cipher' AND r.Dirty = true", - parameters: [{ name: '@id', value: cipher.id }, { name: '@userId', value: userId }] - }; - - var requestOptions = { continuation: continuation }; - var accepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, documents, responseOptions) { - if (err) throw err; - - if (documents.length > 0) { - replace(documents[0], cipher); - } - else if (responseOptions.continuation) { - // try again - queryAndReplace(cipher, responseOptions.continuation); - } - else { - // doc not found, skip it - next(); - } - }); - - if (!accepted) { - response.setBody(count); - } - } - - function replace(doc, placementCipher) { - // site - if (doc.CipherType == 1) { - doc.Username = placementCipher.Username; - doc.Password = placementCipher.Password; - doc.Notes = placementCipher.Notes; - doc.Uri = placementCipher.Uri; - } - - doc.Name = placementCipher.Name; - doc.RevisionDate = placementCipher.RevisionDate; - // no longer dirty - doc.Dirty = false; - - var accepted = collection.replaceDocument(doc._self, doc, function (err) { - if (err) throw err; - - next(); - }); - - if (!accepted) { - response.setBody(count); - } - } - - function next() { - count++; - - if (count >= ciphersLength) { - response.setBody(count); - } - else { - queryAndReplace(ciphers[count]); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/UserRepository.cs b/src/Core/Repositories/DocumentDB/UserRepository.cs deleted file mode 100644 index 11835b585..000000000 --- a/src/Core/Repositories/DocumentDB/UserRepository.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.Core.Repositories.DocumentDB.Utilities; -using Microsoft.Azure.Documents.Client; - -namespace Bit.Core.Repositories.DocumentDB -{ - public class UserRepository : Repository, IUserRepository - { - public UserRepository(DocumentClient client, string databaseId) - : base(client, databaseId) - { } - - public override async Task GetByIdAsync(string id) - { - return await GetByPartitionIdAsync(id); - } - - public async Task GetByEmailAsync(string email) - { - IEnumerable docs = null; - await DocumentDBHelpers.ExecuteWithRetryAsync(() => - { - docs = Client.CreateDocumentQuery(DatabaseUri, new FeedOptions { MaxItemCount = 1 }) - .Where(d => d.Type == Domains.User.TypeValue && d.Email == email).AsEnumerable(); - - return Task.FromResult(0); - }); - - return docs.FirstOrDefault(); - } - - public override async Task DeleteAsync(Domains.User user) - { - await DeleteByIdAsync(user.Id); - } - - public override async Task DeleteByIdAsync(string id) - { - await DocumentDBHelpers.ExecuteWithRetryAsync(async () => - { - while(true) - { - StoredProcedureResponse sprocResponse = await Client.ExecuteStoredProcedureAsync( - ResolveSprocIdLink(id, "bulkDelete"), - string.Format("SELECT * FROM c WHERE c.id = '{0}' OR c.UserId = '{0}'", id)); - - if(!(bool)sprocResponse.Response.continuation) - { - break; - } - } - }); - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Utilities/DocumentDBHelpers.cs b/src/Core/Repositories/DocumentDB/Utilities/DocumentDBHelpers.cs deleted file mode 100644 index db8abbb8a..000000000 --- a/src/Core/Repositories/DocumentDB/Utilities/DocumentDBHelpers.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Newtonsoft.Json; - -namespace Bit.Core.Repositories.DocumentDB.Utilities -{ - public class DocumentDBHelpers - { - public static DocumentClient InitClient(GlobalSettings.DocumentDBSettings settings) - { - var client = new DocumentClient( - new Uri(settings.Uri), - settings.Key, - new ConnectionPolicy - { - ConnectionMode = ConnectionMode.Direct, - ConnectionProtocol = Protocol.Tcp - }); - - var hashResolver = new ManagedHashPartitionResolver( - GetPartitionKeyExtractor(), - settings.DatabaseId, - settings.CollectionIdPrefix, - settings.NumberOfCollections, - null); - - client.PartitionResolvers[UriFactory.CreateDatabaseUri(settings.DatabaseId).OriginalString] = hashResolver; - client.OpenAsync().Wait(); - - return client; - } - - public static async Task ExecuteWithRetryAsync(Func func, int? retryMax = null) - { - var executionAttempt = 1; - while(true) - { - try - { - await func(); - return; - } - catch(DocumentClientException e) - { - await HandleDocumentClientExceptionAsync(e, executionAttempt, retryMax); - } - catch(AggregateException e) - { - var docEx = e.InnerException as DocumentClientException; - if(docEx != null) - { - await HandleDocumentClientExceptionAsync(docEx, executionAttempt, retryMax); - } - } - - executionAttempt++; - } - } - - public static int GetTakeCount(IEnumerable docs, int maxSizeKb = 500) - { - var takeCount = docs.Count(); - while(takeCount > 1) - { - var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(docs.Take(takeCount))); - if((bytes.Length / 1000) <= maxSizeKb) - { - // array is is small enough - break; - } - - takeCount = Convert.ToInt32(Math.Ceiling((double)takeCount / 2)); - } - - return takeCount; - } - - private static async Task HandleDocumentClientExceptionAsync(DocumentClientException e, int retryCount, int? retryMax) - { - if(retryMax.HasValue && retryCount >= retryMax.Value) - { - throw e; - } - - var statusCode = (int)e.StatusCode; - if(statusCode == 429 || statusCode == 503) - { - await Task.Delay(e.RetryAfter); - } - else { - throw e; - } - } - - private static Func GetPartitionKeyExtractor() - { - return doc => - { - if(doc is Domains.User) - { - return ((Domains.User)doc).Id; - } - - if(doc is Domains.Cipher) - { - return ((Domains.Cipher)doc).UserId; - } - - throw new InvalidOperationException("Document type is not resolvable for the partition key extractor."); - }; - } - } -} diff --git a/src/Core/Repositories/DocumentDB/Utilities/ManagedHashPartitionResolver.cs b/src/Core/Repositories/DocumentDB/Utilities/ManagedHashPartitionResolver.cs deleted file mode 100644 index 47394045c..000000000 --- a/src/Core/Repositories/DocumentDB/Utilities/ManagedHashPartitionResolver.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Azure.Documents.Client; -using Microsoft.Azure.Documents.Partitioning; - -namespace Bit.Core.Repositories.DocumentDB.Utilities -{ - public class ManagedHashPartitionResolver : HashPartitionResolver - { - public ManagedHashPartitionResolver( - Func partitionKeyExtractor, - string databaseId, - string collectionIdPrefix, - int numberOfCollections, - IHashGenerator hashGenerator = null) - : base( - partitionKeyExtractor, - GetCollectionIds(databaseId, collectionIdPrefix, numberOfCollections), - 128, - hashGenerator) - { } - - private static List GetCollectionIds(string databaseId, string collectionIdPrefix, int numberOfCollections) - { - var collections = new List(); - for(int i = 0; i < numberOfCollections; i++) - { - var collectionIdUri = UriFactory.CreateDocumentCollectionUri(databaseId, string.Concat(collectionIdPrefix, i)); - collections.Add(collectionIdUri.OriginalString); - } - - return collections; - } - } -} diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index 42fc92166..ca1b346b6 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Bit.Core.Repositories.SqlServer.Models; using DataTableProxy; using Bit.Core.Domains; +using System.Data; namespace Bit.Core.Repositories.SqlServer { @@ -28,6 +29,9 @@ namespace Bit.Core.Repositories.SqlServer return Task.FromResult(0); } + // Get the id of the expected user + var userId = ((Cipher)ciphers.First()).UserId; + using(var connection = new SqlConnection(ConnectionString)) { connection.Open(); @@ -92,6 +96,8 @@ namespace Bit.Core.Repositories.SqlServer [dbo].[Folder] F INNER JOIN #TempFolder TF ON F.Id = TF.Id + WHERE + F.[UserId] = @UserId UPDATE [dbo].[Site] @@ -109,12 +115,15 @@ namespace Bit.Core.Repositories.SqlServer [dbo].[Site] S INNER JOIN #TempSite TS ON S.Id = TS.Id + WHERE + S.[UserId] = @UserId DROP TABLE #TempFolder DROP TABLE #TempSite"; using(var cmd = new SqlCommand(sqlUpdate, connection, transaction)) { + cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = new Guid(userId); cmd.ExecuteNonQuery(); } diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index abd73ed18..9be1583c1 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -5,8 +5,6 @@ namespace Bit.Core.Services { public interface IMailService { - Task SendAlreadyRegisteredEmailAsync(string registrantEmailAddress); - Task SendRegisterEmailAsync(string registrantEmailAddress, string token); Task SendWelcomeEmailAsync(User user); Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail); Task SendChangeEmailEmailAsync(string newEmailAddress, string token); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 9ca79570e..fcedff43d 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -10,8 +10,7 @@ namespace Bit.Core.Services { Task GetUserByIdAsync(string userId); Task SaveUserAsync(User user); - Task InitiateRegistrationAsync(string email); - Task RegisterUserAsync(string token, User user, string masterPassword); + Task RegisterUserAsync(User user, string masterPassword); Task SendMasterPasswordHintAsync(string email); Task InitiateEmailChangeAsync(User user, string newEmail); Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable ciphers); diff --git a/src/Core/Services/MailService.cs b/src/Core/Services/MailService.cs index 6bf686c20..b207880db 100644 --- a/src/Core/Services/MailService.cs +++ b/src/Core/Services/MailService.cs @@ -9,8 +9,6 @@ namespace Bit.Core.Services { public class MailService : IMailService { - private const string AlreadyRegisteredTemplateId = "8af9cd2b-e4dd-497a-bcc6-1d5b317ff811"; - private const string RegisterTemplateId = "7382e1f9-50c7-428d-aa06-bf584f03cd6a"; private const string WelcomeTemplateId = "d24aa21e-5ead-45d8-a14e-f96ba7ec63ff"; private const string ChangeEmailAlreadyExistsTemplateId = "b28bc69e-9592-4320-b274-bfb955667add"; private const string ChangeEmailTemplateId = "b8d17dd7-c883-4b47-8170-5b845d487929"; @@ -29,32 +27,6 @@ namespace Bit.Core.Services _web = new Web(_globalSettings.Mail.ApiKey); } - public async Task SendAlreadyRegisteredEmailAsync(string registrantEmailAddress) - { - var message = CreateDefaultMessage(AlreadyRegisteredTemplateId); - - message.Subject = "Your Registration"; - message.AddTo(registrantEmailAddress); - message.AddSubstitution("{{email}}", new List { registrantEmailAddress }); - message.SetCategories(new List { AdministrativeCategoryName, "Already Registered" }); - - await _web.DeliverAsync(message); - } - - public async Task SendRegisterEmailAsync(string registrantEmailAddress, string token) - { - var message = CreateDefaultMessage(RegisterTemplateId); - - message.Subject = "Complete Your Registration"; - message.AddTo(registrantEmailAddress); - message.AddSubstitution("{{token}}", new List { Uri.EscapeDataString(token) }); - message.AddSubstitution("{{email}}", new List { Uri.EscapeDataString(registrantEmailAddress) }); - message.SetCategories(new List { AdministrativeCategoryName, "Register" }); - message.DisableBypassListManagement(); - - await _web.DeliverAsync(message); - } - public async Task SendWelcomeEmailAsync(User user) { var message = CreateDefaultMessage(WelcomeTemplateId); diff --git a/src/Core/Services/UserService.cs b/src/Core/Services/UserService.cs index a7d3c80e7..204ff96f4 100644 --- a/src/Core/Services/UserService.cs +++ b/src/Core/Services/UserService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; using Microsoft.AspNet.Identity; using Microsoft.Extensions.Logging; @@ -19,7 +18,6 @@ namespace Bit.Core.Services private readonly IUserRepository _userRepository; private readonly ICipherRepository _cipherRepository; private readonly IMailService _mailService; - private readonly ITimeLimitedDataProtector _registrationEmailDataProtector; private readonly IdentityErrorDescriber _identityErrorDescriber; private readonly IdentityOptions _identityOptions; private readonly IPasswordHasher _passwordHasher; @@ -29,7 +27,6 @@ namespace Bit.Core.Services IUserRepository userRepository, ICipherRepository cipherRepository, IMailService mailService, - IDataProtectionProvider dataProtectionProvider, IUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, @@ -55,7 +52,6 @@ namespace Bit.Core.Services _userRepository = userRepository; _cipherRepository = cipherRepository; _mailService = mailService; - _registrationEmailDataProtector = dataProtectionProvider.CreateProtector("RegistrationEmail").ToTimeLimitedDataProtector(); _identityOptions = optionsAccessor?.Value ?? new IdentityOptions(); _identityErrorDescriber = errors; _passwordHasher = passwordHasher; @@ -73,38 +69,12 @@ namespace Bit.Core.Services { throw new ApplicationException("Use register method to create a new user."); } - + await _userRepository.ReplaceAsync(user); } - public async Task InitiateRegistrationAsync(string email) + public async Task RegisterUserAsync(User user, string masterPassword) { - var existingUser = await _userRepository.GetByEmailAsync(email); - if(existingUser != null) - { - await _mailService.SendAlreadyRegisteredEmailAsync(email); - return; - } - - var token = _registrationEmailDataProtector.Protect(email, TimeSpan.FromDays(5)); - await _mailService.SendRegisterEmailAsync(email, token); - } - - public async Task RegisterUserAsync(string token, User user, string masterPassword) - { - try - { - var tokenEmail = _registrationEmailDataProtector.Unprotect(token); - if(tokenEmail != user.Email) - { - return IdentityResult.Failed(_identityErrorDescriber.InvalidToken()); - } - } - catch - { - return IdentityResult.Failed(_identityErrorDescriber.InvalidToken()); - } - var result = await base.CreateAsync(user, masterPassword); if(result == IdentityResult.Success) { @@ -163,8 +133,6 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(newEmail)); } - user.OldEmail = user.Email; - user.OldMasterPassword = user.MasterPassword; user.Email = newEmail; user.MasterPassword = _passwordHasher.HashPassword(user, newMasterPassword); user.SecurityStamp = Guid.NewGuid().ToString(); @@ -279,7 +247,6 @@ namespace Bit.Core.Services } } - user.OldMasterPassword = user.MasterPassword; user.MasterPassword = _passwordHasher.HashPassword(user, newPassword); user.SecurityStamp = Guid.NewGuid().ToString(); diff --git a/src/Core/project.json b/src/Core/project.json index fc3d946a0..13b431966 100644 --- a/src/Core/project.json +++ b/src/Core/project.json @@ -12,9 +12,6 @@ "OtpSharp": "1.3.0.4", "Microsoft.AspNet.Mvc.Abstractions": "6.0.0-rc1-final", "Sendgrid": "6.3.4", - "Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc1-final", - "Microsoft.Azure.DocumentDB": "1.5.2", - "Newtonsoft.Json": "8.0.1", "Dapper": "1.42.0", "DataTableProxy": "1.2.0" }, diff --git a/src/Sql/dbo/Views/SiteView.sql b/src/Sql/dbo/Views/SiteView.sql index 26e779468..02cb44fd1 100644 --- a/src/Sql/dbo/Views/SiteView.sql +++ b/src/Sql/dbo/Views/SiteView.sql @@ -3,4 +3,4 @@ AS SELECT * FROM - [dbo].[Site] \ No newline at end of file + [dbo].[Site]