From fecd5b3a1a4d48c232b742bde33684bec4c6f263 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 8 Aug 2017 17:27:01 -0400 Subject: [PATCH] local attachment storage & docker image --- attachments/Dockerfile | 8 + attachments/build.ps1 | 7 + attachments/build.sh | 10 ++ attachments/entrypoint.sh | 4 + docker/docker-compose.linux.yml | 3 + docker/docker-compose.override.yml | 3 + docker/docker-compose.windows.yml | 3 + docker/docker-compose.yml | 5 + src/Core/GlobalSettings.cs | 1 + .../LocalAttachmentStorageService.cs | 155 ++++++++++++++++++ src/Core/Utilities/CoreHelpers.cs | 19 +++ .../Utilities/ServiceCollectionExtensions.cs | 35 +++- util/Setup/Program.cs | 14 ++ 13 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 attachments/Dockerfile create mode 100644 attachments/build.ps1 create mode 100644 attachments/build.sh create mode 100644 attachments/entrypoint.sh create mode 100644 src/Core/Services/Implementations/LocalAttachmentStorageService.cs diff --git a/attachments/Dockerfile b/attachments/Dockerfile new file mode 100644 index 000000000..e73699702 --- /dev/null +++ b/attachments/Dockerfile @@ -0,0 +1,8 @@ +FROM node + +RUN npm install http-server -g +EXPOSE 80 + +COPY entrypoint.sh / +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/attachments/build.ps1 b/attachments/build.ps1 new file mode 100644 index 000000000..6f7faef99 --- /dev/null +++ b/attachments/build.ps1 @@ -0,0 +1,7 @@ +$dir = Split-Path -Parent $MyInvocation.MyCommand.Path + +echo "`n# Building Attachments" + +echo "`nBuilding docker image" +docker --version +docker build -t bitwarden/attachments $dir\. diff --git a/attachments/build.sh b/attachments/build.sh new file mode 100644 index 000000000..9f0840545 --- /dev/null +++ b/attachments/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e + +DIR="$(dirname $(readlink -f $0))" + +echo -e "\n# Building Attachments" + +echo -e "\nBuilding docker image" +docker --version +docker build -t bitwarden/attachments $DIR/. diff --git a/attachments/entrypoint.sh b/attachments/entrypoint.sh new file mode 100644 index 000000000..748d7c371 --- /dev/null +++ b/attachments/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +http-server /etc/bitwarden/core/attachments/. -p 80 -d false --utc + diff --git a/docker/docker-compose.linux.yml b/docker/docker-compose.linux.yml index d9ab2fe5d..b85b6eff4 100644 --- a/docker/docker-compose.linux.yml +++ b/docker/docker-compose.linux.yml @@ -7,6 +7,9 @@ services: web: volumes: - /etc/bitwarden/web:/etc/bitwarden/web + attachments: + volumes: + - /etc/bitwarden/core/attachments:/etc/bitwarden/core/attachments api: volumes: - /etc/bitwarden/core:/etc/bitwarden/core diff --git a/docker/docker-compose.override.yml b/docker/docker-compose.override.yml index 8baa6cdc1..6d2239f58 100644 --- a/docker/docker-compose.override.yml +++ b/docker/docker-compose.override.yml @@ -7,6 +7,9 @@ services: web: volumes: - c:/bitwarden/web:/etc/bitwarden/web + attachments: + volumes: + - c:/bitwarden/core/attachments:/etc/bitwarden/core/attachments api: volumes: - c:/bitwarden/core:/etc/bitwarden/core diff --git a/docker/docker-compose.windows.yml b/docker/docker-compose.windows.yml index 8baa6cdc1..6d2239f58 100644 --- a/docker/docker-compose.windows.yml +++ b/docker/docker-compose.windows.yml @@ -7,6 +7,9 @@ services: web: volumes: - c:/bitwarden/web:/etc/bitwarden/web + attachments: + volumes: + - c:/bitwarden/core/attachments:/etc/bitwarden/core/attachments api: volumes: - c:/bitwarden/core:/etc/bitwarden/core diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8f40a9b27..b309c27b6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -15,6 +15,11 @@ services: image: bitwarden/web container_name: web restart: always + + attachments: + image: bitwarden/attachments + container_name: attachments + restart: always api: image: bitwarden/api diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index c4be85ab2..46b89db31 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -39,6 +39,7 @@ public class AttachmentSettings { public string ConnectionString { get; set; } + public string BaseDirectory { get; set; } public string BaseUrl { get; set; } } diff --git a/src/Core/Services/Implementations/LocalAttachmentStorageService.cs b/src/Core/Services/Implementations/LocalAttachmentStorageService.cs new file mode 100644 index 000000000..ad6c641aa --- /dev/null +++ b/src/Core/Services/Implementations/LocalAttachmentStorageService.cs @@ -0,0 +1,155 @@ +using System.Threading.Tasks; +using System.IO; +using System; +using Bit.Core.Models.Table; + +namespace Bit.Core.Services +{ + public class LocalAttachmentStorageService : IAttachmentStorageService + { + private readonly string _baseDirPath; + private readonly string _baseTempDirPath; + + public LocalAttachmentStorageService( + GlobalSettings globalSettings) + { + _baseDirPath = globalSettings.Attachment.BaseDirectory; + _baseTempDirPath = $"{_baseDirPath}/temp"; + } + + public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, string attachmentId) + { + await InitAsync(); + var cipherDirPath = $"{_baseDirPath}/{cipher.Id}"; + CreateDirectoryIfNotExists(cipherDirPath); + + using(var fs = File.Create($"{cipherDirPath}/{attachmentId}")) + { + stream.Seek(0, SeekOrigin.Begin); + stream.CopyTo(fs); + } + } + + public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId) + { + await InitAsync(); + var tempCipherOrgDirPath = $"{_baseTempDirPath}/{cipherId}/{organizationId}"; + CreateDirectoryIfNotExists(tempCipherOrgDirPath); + + using(var fs = File.Create($"{tempCipherOrgDirPath}/{attachmentId}")) + { + stream.Seek(0, SeekOrigin.Begin); + stream.CopyTo(fs); + } + } + + public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId) + { + await InitAsync(); + var sourceFilePath = $"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}"; + if(!File.Exists(sourceFilePath)) + { + return; + } + + var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}"; + if(!File.Exists(destFilePath)) + { + return; + } + + var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}"; + DeleteFileIfExists(originalFilePath); + + File.Move(destFilePath, originalFilePath); + DeleteFileIfExists(destFilePath); + + File.Move(sourceFilePath, destFilePath); + } + + public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId) + { + await InitAsync(); + DeleteFileIfExists($"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}"); + + var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}"; + if(!File.Exists(originalFilePath)) + { + return; + } + + var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}"; + DeleteFileIfExists(destFilePath); + + File.Move(originalFilePath, destFilePath); + DeleteFileIfExists(originalFilePath); + } + + public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId) + { + await InitAsync(); + DeleteFileIfExists($"{_baseDirPath}/{cipherId}/{attachmentId}"); + } + + public async Task CleanupAsync(Guid cipherId) + { + await InitAsync(); + DeleteDirectoryIfExists($"{_baseTempDirPath}/{cipherId}"); + } + + public async Task DeleteAttachmentsForCipherAsync(Guid cipherId) + { + await InitAsync(); + DeleteDirectoryIfExists($"{_baseDirPath}/{cipherId}"); + } + + public async Task DeleteAttachmentsForOrganizationAsync(Guid organizationId) + { + await InitAsync(); + } + + public async Task DeleteAttachmentsForUserAsync(Guid userId) + { + await InitAsync(); + } + + private void DeleteFileIfExists(string path) + { + if(File.Exists(path)) + { + File.Delete(path); + } + } + + private void DeleteDirectoryIfExists(string path) + { + if(Directory.Exists(path)) + { + Directory.Delete(path, true); + } + } + + private void CreateDirectoryIfNotExists(string path) + { + if(!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + } + + private Task InitAsync() + { + if(!Directory.Exists(_baseDirPath)) + { + Directory.CreateDirectory(_baseDirPath); + } + + if(!Directory.Exists(_baseTempDirPath)) + { + Directory.CreateDirectory(_baseTempDirPath); + } + + return Task.FromResult(0); + } + } +} diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 206c8d9ed..2e277541f 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -268,5 +268,24 @@ namespace Bit.Core.Utilities { return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); } + + public static bool FullFramework() + { +#if NET461 + return true; +#else + return false; +#endif + } + + public static bool SettingHasValue(string setting) + { + if(string.IsNullOrWhiteSpace(setting) || setting.Equals("SECRET")) + { + return false; + } + + return true; + } } } diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index e78521629..a7f81ca50 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -15,7 +15,9 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +#if NET461 using Microsoft.WindowsAzure.Storage; +#endif using System; using System.IO; using SqlServerRepos = Bit.Core.Repositories.SqlServer; @@ -53,23 +55,38 @@ namespace Bit.Core.Utilities { services.AddSingleton(); - if(!string.IsNullOrWhiteSpace(globalSettings.Mail.SendGridApiKey)) + if(CoreHelpers.SettingHasValue(globalSettings.Mail.SendGridApiKey)) { services.AddSingleton(); } + else if(CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Host) && + CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Username) && + CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Password)) + { + services.AddSingleton(); + } else { services.AddSingleton(); } #if NET461 - services.AddSingleton(); - services.AddSingleton(); + if(globalSettings.SelfHosted) + { + services.AddSingleton(); + services.AddSingleton(); + } + else + { + services.AddSingleton(); + services.AddSingleton(); + } #else services.AddSingleton(); services.AddSingleton(); #endif - if(!string.IsNullOrWhiteSpace(globalSettings.Storage.ConnectionString)) + + if(CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString)) { services.AddSingleton(); } @@ -78,10 +95,14 @@ namespace Bit.Core.Utilities services.AddSingleton(); } - if(!string.IsNullOrWhiteSpace(globalSettings.Attachment.ConnectionString)) + if(CoreHelpers.SettingHasValue(globalSettings.Attachment.ConnectionString)) { services.AddSingleton(); } + else if(CoreHelpers.SettingHasValue(globalSettings.Attachment.BaseDirectory)) + { + services.AddSingleton(); + } else { services.AddSingleton(); @@ -169,8 +190,8 @@ namespace Bit.Core.Utilities { identityServerBuilder.AddTemporarySigningCredential(); } - else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificatePassword) && - System.IO.File.Exists("identity.pfx")) + else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificatePassword) + && File.Exists("identity.pfx")) { var identityServerCert = CoreHelpers.GetCertificate("identity.pfx", globalSettings.IdentityServer.CertificatePassword); diff --git a/util/Setup/Program.cs b/util/Setup/Program.cs index 85aebca18..2679f1f9d 100644 --- a/util/Setup/Program.cs +++ b/util/Setup/Program.cs @@ -204,6 +204,16 @@ server {{ proxy_redirect off; }} + location /attachments/ {{ + proxy_pass http://attachments/; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Url-Scheme $scheme; + proxy_redirect off; + }} + location /api/ {{ proxy_pass http://api/; proxy_set_header X-Real-IP $remote_addr; @@ -243,6 +253,8 @@ globalSettings:baseServiceUri:api={_url}/api globalSettings:baseServiceUri:identity={_url}/identity globalSettings:sqlServer:connectionString={dbConnectionString} globalSettings:identityServer:certificatePassword={_identityCertPassword} +globalSettings:attachment:baseDirectory=/etc/bitwarden/core/attachments +globalSettings:attachment:baseUrl={_url}/attachments globalSettings:duo:aKey={Helpers.SecureRandomString(32, alpha: true, numeric: true)} globalSettings:yubico:clientId=REPLACE globalSettings:yubico:REPLACE"); @@ -265,6 +277,8 @@ SA_PASSWORD={dbPass}"); sw.Write($@"var bitwardenAppSettings = {{ apiUri: ""{_url}/api"", identityUri: ""{_url}/identity"", + stripeKey: null, + braintreeKey: null, whitelistDomains: [""{_domain}""] }};"); }