Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/ByteSync.Client/Assets/Resources/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/ByteSync.Client/Assets/Resources/Resources.fr.resx
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,12 @@ Vous devez sélectionner au minimum 2 Nœuds de Données.</value>
<value>Il n'est pas possible d'ajouter cet élément pour la raison suivante :
Soit cet élément, soit un parent, soit un descendant est déjà présent.</value>
</data>
<data name="DataSourceChecker_ProtectedPathError_Title" xml:space="preserve">
<value>Impossible d'ajouter la source</value>
</data>
<data name="DataSourceChecker_ProtectedPathError_Message" xml:space="preserve">
<value>Le chemin sélectionné "{0}" correspond à un emplacement système protégé et ne peut pas être inventorié ni synchronisé.</value>
</data>
<data name="DataTypes_FilesDirectories" xml:space="preserve">
<value>Fichiers et répertoires</value>
</data>
Expand Down
6 changes: 6 additions & 0 deletions src/ByteSync.Client/Assets/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,12 @@ You must select at least 2 Data Nodes.</value>
<value>It is not possible to add this element for the following reason:
Either this element, or a parent, or a descendant is already present.</value>
</data>
<data name="DataSourceChecker_ProtectedPathError_Title" xml:space="preserve">
<value>Unable to add the data source</value>
</data>
<data name="DataSourceChecker_ProtectedPathError_Message" xml:space="preserve">
<value>The selected path "{0}" points to a protected system location and cannot be inventoried or synchronized.</value>
</data>
<data name="DataTypes_FilesDirectories" xml:space="preserve">
<value>Files and directories</value>
</data>
Expand Down
18 changes: 15 additions & 3 deletions src/ByteSync.Client/Factories/InventoryBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
using ByteSync.Interfaces.Factories;
using ByteSync.Interfaces.Repositories;
using ByteSync.Interfaces.Services.Sessions;
using Microsoft.Extensions.Logging;

namespace ByteSync.Factories;

public class InventoryBuilderFactory : IInventoryBuilderFactory
{
private readonly IComponentContext _context;
private readonly ILogger<InventoryBuilderFactory> _logger;

public InventoryBuilderFactory(IComponentContext context)
public InventoryBuilderFactory(IComponentContext context, ILogger<InventoryBuilderFactory> logger)
{
_context = context;
_logger = logger;
}

public IInventoryBuilder CreateInventoryBuilder(DataNode dataNode)
Expand Down Expand Up @@ -54,9 +57,18 @@ public IInventoryBuilder CreateInventoryBuilder(DataNode dataNode)

foreach (var dataSource in myDataSources)
{
inventoryBuilder.AddInventoryPart(dataSource);
try
{
inventoryBuilder.AddInventoryPart(dataSource);
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"InventoryBuilderFactory: Failed to add data source {Path} for DataNode {DataNodeId}",
dataSource.Path, dataNode.Id);
}
}

return inventoryBuilder;
}
}
}
67 changes: 67 additions & 0 deletions src/ByteSync.Client/Helpers/ProtectedPaths.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.IO;
using ByteSync.Common.Business.Misc;

namespace ByteSync.Helpers;

public static class ProtectedPaths
{
private static readonly string[] _protectedRoots =
[
"/dev",
"/proc",
"/sys",
"/run",
"/var/run",
"/private/var/run"
];

public static bool TryGetProtectedRoot(string path, OSPlatforms osPlatform, out string protectedRoot)
{
protectedRoot = string.Empty;

if (osPlatform == OSPlatforms.Windows)
{
return false;
}

if (string.IsNullOrWhiteSpace(path))
{
return false;
}

var normalizedPath = Normalize(path);

foreach (var root in _protectedRoots)
{
var normalizedRoot = Normalize(root);

if (IsSameOrSubPath(normalizedPath, normalizedRoot, StringComparison.Ordinal))
{
protectedRoot = root;

return true;
}
}

return false;
}

private static string Normalize(string path)
{
var fullPath = Path.GetFullPath(path);

return Path.TrimEndingDirectorySeparator(fullPath);
}

private static bool IsSameOrSubPath(string path, string root, StringComparison comparison)
{
if (path.Equals(root, comparison))
{
return true;
}

var rootWithSeparator = root + Path.DirectorySeparatorChar;

return path.StartsWith(rootWithSeparator, comparison);
}
}
49 changes: 38 additions & 11 deletions src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,81 @@
using ByteSync.Assets.Resources;
using ByteSync.Assets.Resources;
using ByteSync.Business.DataSources;
using ByteSync.Common.Business.Inventories;
using ByteSync.Helpers;
using ByteSync.Interfaces;
using ByteSync.Interfaces.Controls.Applications;
using ByteSync.Interfaces.Dialogs;

namespace ByteSync.Services.Inventories;

public class DataSourceChecker : IDataSourceChecker
{
private readonly IDialogService _dialogService;
private readonly IEnvironmentService _environmentService;
private readonly ILogger<DataSourceChecker> _logger;

public DataSourceChecker(IDialogService dialogService)
public DataSourceChecker(IDialogService dialogService, IEnvironmentService environmentService, ILogger<DataSourceChecker> logger)
{
_dialogService = dialogService;
_environmentService = environmentService;
_logger = logger;
}

public async Task<bool> CheckDataSource(DataSource dataSource, IEnumerable<DataSource> existingDataSources)
{
if (dataSource.ClientInstanceId == _environmentService.ClientInstanceId
&& ProtectedPaths.TryGetProtectedRoot(dataSource.Path, _environmentService.OSPlatform, out var protectedRoot))
{
_logger.LogWarning("Blocked data source path {Path} because it is under protected root {ProtectedRoot}",
dataSource.Path, protectedRoot);
await ShowProtectedPathError(dataSource.Path);

return false;
}

if (dataSource.Type == FileSystemTypes.File)
{
if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && ds.Type == FileSystemTypes.File
&& ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase)))
&& ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase)))
{
await ShowError();

return false;
}
}
else
{
// We can neither be equal, nor be, nor be a parent of an already selected path
if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && ds.Type == FileSystemTypes.Directory
&& (ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase) ||
IOUtils.IsSubPathOf(ds.Path, dataSource.Path) ||
IOUtils.IsSubPathOf(dataSource.Path, ds.Path))))
if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) &&
ds.Type == FileSystemTypes.Directory
&& (ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase) ||
IOUtils.IsSubPathOf(ds.Path, dataSource.Path) ||
IOUtils.IsSubPathOf(dataSource.Path, ds.Path))))
{
await ShowError();

return false;
}
}

return true;
}

private async Task ShowError()
{
var messageBoxViewModel = _dialogService.CreateMessageBoxViewModel(
nameof(Resources.DataSourceChecker_SubPathError_Title), nameof(Resources.DataSourceChecker_SubPathError_Message));
messageBoxViewModel.ShowOK = true;
await _dialogService.ShowMessageBoxAsync(messageBoxViewModel);
}

private async Task ShowProtectedPathError(string path)
{
var messageBoxViewModel = _dialogService.CreateMessageBoxViewModel(
nameof(Resources.DataSourceChecker_ProtectedPathError_Title),
nameof(Resources.DataSourceChecker_ProtectedPathError_Message),
path);
messageBoxViewModel.ShowOK = true;
await _dialogService.ShowMessageBoxAsync(messageBoxViewModel);
}
}
11 changes: 11 additions & 0 deletions src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using ByteSync.Business.Sessions;
using ByteSync.Common.Business.Inventories;
using ByteSync.Common.Business.Misc;
using ByteSync.Helpers;
using ByteSync.Interfaces.Controls.Inventories;
using ByteSync.Models.FileSystems;
using ByteSync.Models.Inventories;
Expand Down Expand Up @@ -114,6 +115,16 @@ public InventoryPart AddInventoryPart(string fullName)
{
InventoryPart inventoryPart;

if (ProtectedPaths.TryGetProtectedRoot(fullName, OSPlatform, out var protectedRoot))
{
_logger.LogWarning(
"InventoryBuilder.AddInventoryPart: Path {Path} is under protected root {ProtectedRoot} and will be rejected",
fullName, protectedRoot);

throw new InvalidOperationException(
$"Path '{fullName}' is under protected root '{protectedRoot}'");
}

if (Directory.Exists(fullName))
{
inventoryPart = new InventoryPart(Inventory, fullName, FileSystemTypes.Directory);
Expand Down
5 changes: 3 additions & 2 deletions src/ByteSync.Common/Business/Versions/ProtocolVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ namespace ByteSync.Common.Business.Versions;
public static class ProtocolVersion
{
public const int V1 = 1;
public const int V2 = 2;

public const int CURRENT = V1;
public const int CURRENT = V2;

public const int MIN_SUPPORTED = V1;
public const int MIN_SUPPORTED = V2;

public static bool IsCompatible(int otherVersion)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private void RegisterClientTypes()

_builder.RegisterGeneric(typeof(Mock<>)).SingleInstance();
_builder.Register(_ => new Mock<ILogger<InventoryBuilder>>().Object).As<ILogger<InventoryBuilder>>();
_builder.Register(_ => new Mock<ILogger<InventoryBuilderFactory>>().Object).As<ILogger<InventoryBuilderFactory>>();
_builder.Register(_ => new Mock<ILogger<InventoryFileAnalyzer>>().Object).As<ILogger<InventoryFileAnalyzer>>();
_builder.Register(_ => new Mock<ILogger<InventoryService>>().Object).As<ILogger<InventoryService>>();

Expand Down Expand Up @@ -260,4 +261,4 @@ public void CreateInventoryBuilder_ShouldResolveSharedDependencies()
inventoryBuilder2.Should().NotBeNull();
inventoryBuilder1.Should().NotBeSameAs(inventoryBuilder2);
}
}
}
56 changes: 56 additions & 0 deletions tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using ByteSync.Common.Business.Misc;
using ByteSync.Helpers;
using FluentAssertions;
using NUnit.Framework;

namespace ByteSync.Client.UnitTests.Helpers;

[TestFixture]
public class ProtectedPathsTests
{
[TestCase("/dev", "/dev")]
[TestCase("/dev/sda", "/dev")]
[TestCase("/proc", "/proc")]
[TestCase("/proc/1", "/proc")]
[TestCase("/sys", "/sys")]
[TestCase("/sys/kernel", "/sys")]
[TestCase("/run", "/run")]
[TestCase("/run/user/1000", "/run")]
[TestCase("/var/run", "/var/run")]
[TestCase("/var/run/daemon", "/var/run")]
public void TryGetProtectedRoot_Linux_ReturnsTrue(string path, string expectedRoot)
{
var result = ProtectedPaths.TryGetProtectedRoot(path, OSPlatforms.Linux, out var root);

result.Should().BeTrue();
root.Should().Be(expectedRoot);
}

[TestCase("/private/var/run", "/private/var/run")]
[TestCase("/private/var/run/daemon", "/private/var/run")]
public void TryGetProtectedRoot_MacOs_ReturnsTrue(string path, string expectedRoot)
{
var result = ProtectedPaths.TryGetProtectedRoot(path, OSPlatforms.MacOs, out var root);

result.Should().BeTrue();
root.Should().Be(expectedRoot);
}

[Test]
public void TryGetProtectedRoot_Linux_WithNonProtectedPath_ReturnsFalse()
{
var result = ProtectedPaths.TryGetProtectedRoot("/home/user", OSPlatforms.Linux, out var root);

result.Should().BeFalse();
root.Should().BeEmpty();
}

[Test]
public void TryGetProtectedRoot_Windows_ReturnsFalse()
{
var result = ProtectedPaths.TryGetProtectedRoot("C:\\Windows", OSPlatforms.Windows, out var root);

result.Should().BeFalse();
root.Should().BeEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void SetUp()
public async Task OnTrustPublicKeyRequestedAsync_WithIncompatibleProtocolVersion_ShouldThrowInvalidOperationException()
{
var sessionId = "TestSessionId";
var incompatibleVersion = 2;
var incompatibleVersion = ProtocolVersion.CURRENT - 1;
var joinerInstanceId = "JoinerInstanceId";

var myPublicKeyCheckData = new PublicKeyCheckData
Expand Down Expand Up @@ -180,7 +180,7 @@ await FluentActions.Invoking(async () => await _publicKeysTruster.OnTrustPublicK
public async Task TrustAllMembersPublicKeys_WithIncompatibleProtocolVersion_ShouldReturnIncompatibleProtocolVersion()
{
var sessionId = "TestSessionId";
var incompatibleVersion = 2;
var incompatibleVersion = ProtocolVersion.CURRENT - 1;

_sessionMemberApiClient
.Setup(c => c.GetMembersClientInstanceIds(sessionId, It.IsAny<CancellationToken>()))
Expand Down Expand Up @@ -427,7 +427,7 @@ public async Task OnPublicKeyCheckDataAskedAsync_WithIncompatibleProtocolVersion
{
var sessionId = "TestSessionId";
var clientInstanceId = "ClientInstanceId";
var incompatibleVersion = 2;
var incompatibleVersion = ProtocolVersion.CURRENT - 1;

var publicKeyInfo = new PublicKeyInfo
{
Expand Down Expand Up @@ -565,4 +565,4 @@ public async Task TrustAllMembersPublicKeys_WhenProtocolVersionIncompatibleNotif

result.Status.Should().Be(JoinSessionStatus.IncompatibleProtocolVersion);
}
}
}
Loading
Loading