-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathAzureSendFileStorageService.cs
More file actions
155 lines (131 loc) · 5.74 KB
/
AzureSendFileStorageService.cs
File metadata and controls
155 lines (131 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Bit.Core.Enums;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Tools.Services;
public class AzureSendFileStorageService(
GlobalSettings globalSettings,
ILogger<AzureSendFileStorageService> logger) : ISendFileStorageService
{
public const string FilesContainerName = "sendfiles";
private static readonly TimeSpan _downloadLinkLiveTime = TimeSpan.FromMinutes(1);
private readonly BlobServiceClient _blobServiceClient = new(globalSettings.Send.ConnectionString);
private readonly ILogger<AzureSendFileStorageService> _logger = logger;
/*
* When this file was made nullable, multiple instances of ! were introduced asserting that
* _sendFilesContainerClient abd the blobClient it is used to construct are not null.
*
* See InitAsync() at end of file which is responsible for assigning value asynchronously ensuring
* _sendFilesContainerClient and blobClient are not null.
*/
private BlobContainerClient? _sendFilesContainerClient;
public FileUploadType FileUploadType => FileUploadType.Azure;
public static string SendIdFromBlobName(string blobName) => blobName.Split('/')[0];
public static string BlobName(Send send, string fileId) => $"{send.Id}/{fileId}";
public async Task UploadNewFileAsync(Stream stream, Send send, string fileId)
{
await InitAsync();
var blobClient = _sendFilesContainerClient!.GetBlobClient(BlobName(send, fileId));
var metadata = new Dictionary<string, string>();
if (send.UserId.HasValue)
{
metadata.Add("userId", send.UserId.Value.ToString());
}
else if (send.OrganizationId.HasValue)
{
metadata.Add("organizationId", send.OrganizationId.Value.ToString());
}
var headers = new BlobHttpHeaders
{
ContentDisposition = $"attachment; filename=\"{fileId}\""
};
await blobClient.UploadAsync(stream, new BlobUploadOptions { Metadata = metadata, HttpHeaders = headers });
}
public async Task DeleteFileAsync(Send send, string fileId) => await DeleteBlobAsync(BlobName(send, fileId));
public async Task DeleteBlobAsync(string blobName)
{
await InitAsync();
var blobClient = _sendFilesContainerClient!.GetBlobClient(blobName);
await blobClient.DeleteIfExistsAsync();
}
public async Task DeleteFilesForOrganizationAsync(Guid organizationId)
{
await InitAsync();
await DeleteBlobsByMetadataAsync("organizationId", organizationId.ToString());
}
public async Task DeleteFilesForUserAsync(Guid userId)
{
await InitAsync();
await DeleteBlobsByMetadataAsync("userId", userId.ToString());
}
private async Task DeleteBlobsByMetadataAsync(string metadataKey, string metadataValue)
{
await foreach (var blobItem in _sendFilesContainerClient!.GetBlobsAsync(BlobTraits.Metadata))
{
if (blobItem.Metadata != null &&
blobItem.Metadata.TryGetValue(metadataKey, out var value) &&
string.Equals(value, metadataValue, StringComparison.OrdinalIgnoreCase))
{
var blob = _sendFilesContainerClient.GetBlobClient(blobItem.Name);
await blob.DeleteIfExistsAsync();
}
}
}
public async Task<string> GetSendFileDownloadUrlAsync(Send send, string fileId)
{
await InitAsync();
var blobClient = _sendFilesContainerClient!.GetBlobClient(BlobName(send, fileId));
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Read, DateTime.UtcNow.Add(_downloadLinkLiveTime));
return sasUri.ToString();
}
public async Task<string> GetSendFileUploadUrlAsync(Send send, string fileId)
{
await InitAsync();
var blobClient = _sendFilesContainerClient!.GetBlobClient(BlobName(send, fileId));
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Create | BlobSasPermissions.Write, DateTime.UtcNow.Add(_downloadLinkLiveTime));
return sasUri.ToString();
}
public async Task<(bool, long)> ValidateFileAsync(Send send, string fileId, long minimum, long maximum)
{
await InitAsync();
var blobClient = _sendFilesContainerClient!.GetBlobClient(BlobName(send, fileId));
try
{
var blobProperties = await blobClient.GetPropertiesAsync();
var metadata = blobProperties.Value.Metadata;
if (send.UserId.HasValue)
{
metadata["userId"] = send.UserId.Value.ToString();
}
else if (send.OrganizationId.HasValue)
{
metadata["organizationId"] = send.OrganizationId.Value.ToString();
}
await blobClient.SetMetadataAsync(metadata);
var headers = new BlobHttpHeaders
{
ContentDisposition = $"attachment; filename=\"{fileId}\""
};
await blobClient.SetHttpHeadersAsync(headers);
var length = blobProperties.Value.ContentLength;
var valid = minimum <= length && length <= maximum;
return (valid, length);
}
catch (Exception ex)
{
_logger.LogError(ex, $"A storage operation failed in {nameof(ValidateFileAsync)}");
return (false, -1);
}
}
private async Task InitAsync()
{
if (_sendFilesContainerClient == null)
{
_sendFilesContainerClient = _blobServiceClient.GetBlobContainerClient(FilesContainerName);
await _sendFilesContainerClient.CreateIfNotExistsAsync(PublicAccessType.None, null, null);
}
}
}