Skip to content

Commit 7bbf1d4

Browse files
author
Stephen Halter
committed
Use Path.GetTempFileName() for 600 Unix file mode
1 parent 43de302 commit 7bbf1d4

File tree

11 files changed

+228
-0
lines changed

11 files changed

+228
-0
lines changed

src/DataProtection/DataProtection/src/Repositories/FileSystemXmlRepository.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Runtime.InteropServices;
89
using System.Xml.Linq;
910
using Microsoft.AspNetCore.DataProtection.Internal;
1011
using Microsoft.Extensions.Logging;
@@ -129,9 +130,17 @@ private void StoreElementCore(XElement element, string filename)
129130
// crashes mid-write, we won't end up with a corrupt .xml file.
130131

131132
Directory.Create(); // won't throw if the directory already exists
133+
132134
var tempFilename = Path.Combine(Directory.FullName, Guid.NewGuid().ToString() + ".tmp");
133135
var finalFilename = Path.Combine(Directory.FullName, filename + ".xml");
134136

137+
// Create a temp file with the correct Unix file mode before moving it to the expected finalFilename.
138+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
139+
{
140+
var tempTempFilename = Path.GetTempFileName();
141+
File.Move(tempTempFilename, tempFilename);
142+
}
143+
135144
try
136145
{
137146
using (var tempFileStream = File.OpenWrite(tempFilename))

src/DataProtection/DataProtection/test/Repositories/FileSystemXmlRepositoryTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.IO;
67
using System.Linq;
78
using System.Runtime.InteropServices;
@@ -158,6 +159,39 @@ public void Logs_DockerEphemeralFolders()
158159
});
159160
}
160161

162+
[ConditionalFact]
163+
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "UnixFileMode is not supported on Windows.")]
164+
public void StoreElement_CreatesFileWithUserOnlyUnixFileMode()
165+
{
166+
WithUniqueTempDirectory(dirInfo =>
167+
{
168+
// Arrange
169+
var element = XElement.Parse("<element1 />");
170+
var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance);
171+
172+
// Act
173+
repository.StoreElement(element, "friendly-name");
174+
175+
// Assert
176+
var fileInfo = Assert.Single(dirInfo.GetFiles());
177+
178+
//Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, fileInfo.UnixFileMode);
179+
var processStartInfo = new ProcessStartInfo
180+
{
181+
FileName = "ls",
182+
Arguments = $"-l {fileInfo.FullName}",
183+
RedirectStandardOutput = true,
184+
UseShellExecute = false
185+
};
186+
var process = Process.Start(processStartInfo);
187+
188+
Assert.NotNull(process);
189+
var output = process!.StandardOutput.ReadToEnd();
190+
process.WaitForExit();
191+
Assert.StartsWith("-rw-------", output);
192+
});
193+
}
194+
161195
/// <summary>
162196
/// Runs a test and cleans up the temp directory afterward.
163197
/// </summary>

src/Http/WebUtilities/src/FileBufferingReadStream.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers;
66
using System.Diagnostics;
77
using System.IO;
8+
using System.Runtime.InteropServices;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.AspNetCore.Internal;
@@ -204,6 +205,14 @@ private Stream CreateTempFile()
204205
}
205206

206207
_tempFileName = Path.Combine(_tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid().ToString() + ".tmp");
208+
209+
// Create a temp file with the correct Unix file mode before moving it to the assigned _tempFileName in the _tempFileDirectory.
210+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
211+
{
212+
var tempTempFileName = Path.GetTempFileName();
213+
File.Move(tempTempFileName, _tempFileName);
214+
}
215+
207216
return new FileStream(_tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 1024 * 16,
208217
FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.SequentialScan);
209218
}

src/Http/WebUtilities/src/FileBufferingWriteStream.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers;
66
using System.Diagnostics;
77
using System.IO;
8+
using System.Runtime.InteropServices;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.AspNetCore.Internal;
@@ -224,6 +225,14 @@ private void EnsureFileStream()
224225
{
225226
var tempFileDirectory = _tempFileDirectoryAccessor();
226227
var tempFileName = Path.Combine(tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid() + ".tmp");
228+
229+
// Create a temp file with the correct Unix file mode before moving it to the assigned tempFileName in the _tempFileDirectory.
230+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
231+
{
232+
var tempTempFileName = Path.GetTempFileName();
233+
File.Move(tempTempFileName, tempFileName);
234+
}
235+
227236
FileStream = new FileStream(
228237
tempFileName,
229238
FileMode.Create,

src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using System;
55
using System.Buffers;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Text;
89
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Testing;
911
using Moq;
1012
using Xunit;
1113

@@ -351,6 +353,47 @@ public async Task FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRente
351353
Assert.False(File.Exists(tempFileName));
352354
}
353355

356+
[ConditionalFact]
357+
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "UnixFileMode is not supported on Windows.")]
358+
public void Read_BufferingContentToDisk_CreatesFileWithUserOnlyUnixFileMode()
359+
{
360+
var inner = MakeStream(1024 * 2);
361+
string tempFileName;
362+
using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
363+
{
364+
var bytes = new byte[1024 * 2];
365+
var read0 = stream.Read(bytes, 0, bytes.Length);
366+
Assert.Equal(bytes.Length, read0);
367+
Assert.Equal(read0, stream.Length);
368+
Assert.Equal(read0, stream.Position);
369+
Assert.False(stream.InMemory);
370+
Assert.NotNull(stream.TempFileName);
371+
372+
var read1 = stream.Read(bytes, 0, bytes.Length);
373+
Assert.Equal(0, read1);
374+
375+
tempFileName = stream.TempFileName!;
376+
Assert.True(File.Exists(tempFileName));
377+
378+
//Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(tempFileName));
379+
var processStartInfo = new ProcessStartInfo
380+
{
381+
FileName = "ls",
382+
Arguments = $"-l {tempFileName}",
383+
RedirectStandardOutput = true,
384+
UseShellExecute = false
385+
};
386+
var process = Process.Start(processStartInfo);
387+
388+
Assert.NotNull(process);
389+
var output = process!.StandardOutput.ReadToEnd();
390+
process.WaitForExit();
391+
Assert.StartsWith("-rw-------", output);
392+
}
393+
394+
Assert.False(File.Exists(tempFileName));
395+
}
396+
354397
private static string GetCurrentDirectory()
355398
{
356399
return AppContext.BaseDirectory;

src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using System.Linq;
88
using System.Text;
99
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Testing;
1011
using Xunit;
12+
using System.Diagnostics;
1113

1214
namespace Microsoft.AspNetCore.WebUtilities
1315
{
@@ -370,6 +372,37 @@ public async Task DrainBufferAsync_WithContentInDisk_CopiesContentFromMemoryStre
370372
Assert.Equal(0, bufferingStream.Length);
371373
}
372374

375+
[ConditionalFact]
376+
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "UnixFileMode is not supported on Windows.")]
377+
public void Write_BufferingContentToDisk_CreatesFileWithUserOnlyUnixFileMode()
378+
{
379+
// Arrange
380+
var input = new byte[] { 1, 2, 3, };
381+
using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
382+
bufferingStream.Write(input, 0, 2);
383+
384+
// Act
385+
bufferingStream.Write(input, 2, 1);
386+
387+
// Assert
388+
Assert.NotNull(bufferingStream.FileStream);
389+
390+
//Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(bufferingStream.FileStream.SafeFileHandle));
391+
var processStartInfo = new ProcessStartInfo
392+
{
393+
FileName = "ls",
394+
Arguments = $"-l {bufferingStream.FileStream!.Name}",
395+
RedirectStandardOutput = true,
396+
UseShellExecute = false
397+
};
398+
var process = Process.Start(processStartInfo);
399+
400+
Assert.NotNull(process);
401+
var output = process!.StandardOutput.ReadToEnd();
402+
process.WaitForExit();
403+
Assert.StartsWith("-rw-------", output);
404+
}
405+
373406
public void Dispose()
374407
{
375408
try

src/Shared/CertificateGeneration/CertificateManager.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ public void ExportCertificate(X509Certificate2 certificate, string path, bool in
383383
throw;
384384
}
385385
}
386+
387+
// Create a temp file with the correct Unix file mode before moving it to the expected path.
388+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
389+
{
390+
var tempFilename = Path.GetTempFileName();
391+
File.Move(tempFilename, path, overwrite: true);
392+
}
393+
386394
try
387395
{
388396
diagnostics?.Debug($"Writing exported certificate to path '{path}'.");
@@ -668,6 +676,7 @@ private static void RemoveCertificateTrustRule(X509Certificate2 certificate)
668676
try
669677
{
670678
var certBytes = certificate.Export(X509ContentType.Cert);
679+
671680
File.WriteAllBytes(certificatePath, certBytes);
672681
var processInfo = new ProcessStartInfo(
673682
MacOSRemoveCertificateTrustCommandLine,

src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.IO;
67
using System.Linq;
78
using System.Runtime.InteropServices;
@@ -241,6 +242,40 @@ public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates()
241242
Assert.Empty(CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
242243
}
243244
}
245+
246+
[ConditionalFact]
247+
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "UnixFileMode is not supported on Windows.")]
248+
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "https://github.com/dotnet/aspnetcore/issues/6720")]
249+
public void EnsureCreateHttpsCertificate_CreatesFileWithUserOnlyUnixFileMode()
250+
{
251+
_fixture.CleanupCertificates();
252+
253+
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesFileWithUserOnlyUnixFileMode) + ".pfx";
254+
var certificatePassword = Guid.NewGuid().ToString();
255+
var now = DateTimeOffset.UtcNow;
256+
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
257+
258+
var result = _manager
259+
.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject, isInteractive: false);
260+
261+
Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode);
262+
Assert.True(File.Exists(CertificateName));
263+
264+
//Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(CertificateName));
265+
var processStartInfo = new ProcessStartInfo
266+
{
267+
FileName = "ls",
268+
Arguments = $"-l {CertificateName}",
269+
RedirectStandardOutput = true,
270+
UseShellExecute = false
271+
};
272+
var process = Process.Start(processStartInfo);
273+
274+
Assert.NotNull(process);
275+
var output = process!.StandardOutput.ReadToEnd();
276+
process.WaitForExit();
277+
Assert.StartsWith("-rw-------", output);
278+
}
244279
}
245280

246281
public class CertFixture : IDisposable

src/Tools/dotnet-user-secrets/src/Internal/SecretsStore.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Runtime.InteropServices;
89
using System.Text;
910
using Microsoft.Extensions.Configuration;
1011
using Microsoft.Extensions.Configuration.UserSecrets;
@@ -46,6 +47,9 @@ public string this[string key]
4647

4748
public int Count => _secrets.Count;
4849

50+
// For testing.
51+
internal string SecretsFilePath => _secretsFilePath;
52+
4953
public bool ContainsKey(string key) => _secrets.ContainsKey(key);
5054

5155
public IEnumerable<KeyValuePair<string, string>> AsEnumerable() => _secrets;
@@ -75,6 +79,13 @@ public virtual void Save()
7579
}
7680
}
7781

82+
// Create a temp file with the correct Unix file mode before moving it to the expected _filePath.
83+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
84+
{
85+
var tempFilename = Path.GetTempFileName();
86+
File.Move(tempFilename, _secretsFilePath, overwrite: true);
87+
}
88+
7889
File.WriteAllText(_secretsFilePath, contents.ToString(), Encoding.UTF8);
7990
}
8091

src/Tools/dotnet-user-secrets/src/Program.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public Program(IConsole console, string workingDirectory)
2929
_workingDirectory = workingDirectory;
3030
}
3131

32+
// For testing.
33+
internal string SecretsFilePath { get; private set; }
34+
3235
public bool TryRun(string[] args, out int returnCode)
3336
{
3437
try
@@ -91,6 +94,10 @@ internal int RunInternal(params string[] args)
9194
var store = new SecretsStore(userSecretsId, reporter);
9295
var context = new Internal.CommandContext(store, reporter, _console);
9396
options.Command.Execute(context);
97+
98+
// For testing.
99+
SecretsFilePath = store.SecretsFilePath;
100+
94101
return 0;
95102
}
96103

0 commit comments

Comments
 (0)