Skip to content

Commit 66c1b5b

Browse files
committed
New authorization actions and support for per-account directory config
- Moved `PasswordAuthorization` back into the `Abstractions` project - Removed `AuthenticatedBy` property from `FtpConnectionData` and `IAccountInformation`
1 parent cda0e3c commit 66c1b5b

File tree

50 files changed

+1784
-84
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1784
-84
lines changed

FubarDev.FtpServer.v3.ncrunchsolution

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<SolutionConfiguration>
2+
<Settings>
3+
<AllowParallelTestExecution>True</AllowParallelTestExecution>
4+
<SolutionConfigured>True</SolutionConfigured>
5+
</Settings>
6+
</SolutionConfiguration>

GlobalAssemblyInfo.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
//-----------------------------------------------------------------------
1+
//-----------------------------------------------------------------------
22
// <copyright file="GlobalAssemblyInfo.cs" company="Fubar Development Junker">
33
// Copyright (c) Fubar Development Junker. All rights reserved.
44
// </copyright>
55
// <author>Mark Junker</author>
66
//-----------------------------------------------------------------------
77

88
using System.Reflection;
9+
using System.Runtime.CompilerServices;
910

1011
[assembly: AssemblyTrademark("")]
1112
[assembly: AssemblyCulture("")]
13+
14+
[assembly: InternalsVisibleTo("FubarDev.FtpServer.Tests")]

samples/TestFtpServer/Program.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313

1414
using FubarDev.FtpServer;
1515
using FubarDev.FtpServer.AccountManagement;
16+
using FubarDev.FtpServer.AccountManagement.Directories.RootPerUser;
17+
using FubarDev.FtpServer.AccountManagement.Directories.SingleRootWithoutHome;
1618
using FubarDev.FtpServer.Authentication;
19+
using FubarDev.FtpServer.FileSystem;
1720
using FubarDev.FtpServer.FileSystem.DotNet;
1821
using FubarDev.FtpServer.FileSystem.GoogleDrive;
1922
using FubarDev.FtpServer.FileSystem.InMemory;
2023
using FubarDev.FtpServer.FileSystem.Unix;
2124
using FubarDev.FtpServer.MembershipProvider.Pam;
25+
using FubarDev.FtpServer.MembershipProvider.Pam.Directories;
2226

2327
using Google.Apis.Auth.OAuth2;
2428
using Google.Apis.Drive.v3;
@@ -62,8 +66,38 @@ private static int Main(string[] args)
6266
}
6367
}
6468
},
65-
"Workarounds",
69+
"PAM authentication workarounds",
6670
{ "no-pam-account-management", "Disable the PAM account management", v => options.NoPamAccountManagement = v != null },
71+
"Directory layout (filesystem, unix))",
72+
{ "l|layout=", "Directory layout", v => {
73+
switch (v)
74+
{
75+
case "default":
76+
case "root":
77+
case "single":
78+
case "single-root":
79+
options.DirectoryLayout = DirectoryLayout.SingleRoot;
80+
break;
81+
case "root-per-user":
82+
case "per-user-root":
83+
case "per-user":
84+
options.DirectoryLayout = DirectoryLayout.RootPerUser;
85+
break;
86+
case "pam":
87+
case "passwd":
88+
case "pam-home":
89+
case "passwd-home":
90+
options.DirectoryLayout = DirectoryLayout.PamHomeDirectory;
91+
break;
92+
case "pam-root":
93+
case "passwd-root":
94+
options.DirectoryLayout = DirectoryLayout.PamHomeDirectoryAsRoot;
95+
break;
96+
default:
97+
throw new ApplicationException("Invalid authentication module");
98+
}
99+
}
100+
},
67101
"Server",
68102
{ "a|address=", "Sets the IP address or host name", v => options.ServerAddress = v },
69103
{ "p|port=", "Sets the listen port", v => options.Port = Convert.ToInt32(v) },
@@ -283,6 +317,32 @@ private static IServiceCollection CreateServices(TestFtpServerOptions options)
283317
.Configure<GoogleDriveOptions>(opt => opt.UseBackgroundUpload = options.UseBackgroundUpload)
284318
.Configure<PamMembershipProviderOptions>(opt => opt.IgnoreAccountManagement = options.NoPamAccountManagement);
285319

320+
switch (options.DirectoryLayout)
321+
{
322+
case DirectoryLayout.SingleRoot:
323+
services.AddSingleton<IAccountDirectoryQuery, SingleRootWithoutHomeAccountDirectoryQuery>();
324+
break;
325+
case DirectoryLayout.RootPerUser:
326+
services
327+
.AddSingleton<IAccountDirectoryQuery, RootPerUserAccountDirectoryQuery>()
328+
.Configure<RootPerUserAccountDirectoryQueryOptions>(opt => opt.AnonymousRootPerEmail = true);
329+
break;
330+
case DirectoryLayout.PamHomeDirectory:
331+
services
332+
.AddSingleton<IAccountDirectoryQuery, PamAccountDirectoryQuery>()
333+
.Configure<PamAccountDirectoryQueryOptions>(opt => opt.AnonymousRootDirectory = "/tmp");
334+
break;
335+
case DirectoryLayout.PamHomeDirectoryAsRoot:
336+
services
337+
.AddSingleton<IAccountDirectoryQuery, PamAccountDirectoryQuery>()
338+
.Configure<PamAccountDirectoryQueryOptions>(opt =>
339+
{
340+
opt.AnonymousRootDirectory = "/tmp";
341+
opt.UserHomeIsRoot = true;
342+
});
343+
break;
344+
}
345+
286346
if (options.ImplicitFtps)
287347
{
288348
services.Decorate<IFtpServer>(

samples/TestFtpServer/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"TestFtpServer": {
44
"commandName": "Project",
5-
"commandLineArgs": "filesystem"
5+
"commandLineArgs": "filesystem -l per-user"
66
}
77
}
88
}

samples/TestFtpServer/TestFtpServerOptions.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ public class TestFtpServerOptions
7676
/// </summary>
7777
public bool DisableUserIdSwitch { get; set; }
7878

79+
/// <summary>
80+
/// Gets or sets the directory layout.
81+
/// </summary>
82+
public DirectoryLayout DirectoryLayout { get; set; }
83+
7984
/// <summary>
8085
/// Gets the requested or the default port.
8186
/// </summary>
@@ -97,6 +102,32 @@ public void Validate()
97102
}
98103
}
99104

105+
/// <summary>
106+
/// The requested directory layout.
107+
/// </summary>
108+
public enum DirectoryLayout
109+
{
110+
/// <summary>
111+
/// A single root directory for all users.
112+
/// </summary>
113+
SingleRoot,
114+
115+
/// <summary>
116+
/// A per-user root directory.
117+
/// </summary>
118+
RootPerUser,
119+
120+
/// <summary>
121+
/// A single root, but with the users home directory as default directory.
122+
/// </summary>
123+
PamHomeDirectory,
124+
125+
/// <summary>
126+
/// Users home directories are root.
127+
/// </summary>
128+
PamHomeDirectoryAsRoot,
129+
}
130+
100131
/// <summary>
101132
/// The selected membership provider.
102133
/// </summary>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <copyright file="GenericAccountDirectories.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using FubarDev.FtpServer.FileSystem;
6+
7+
using JetBrains.Annotations;
8+
9+
namespace FubarDev.FtpServer.AccountManagement.Directories
10+
{
11+
/// <summary>
12+
/// Default implementation of <see cref="IAccountDirectories"/>.
13+
/// </summary>
14+
public class GenericAccountDirectories : IAccountDirectories
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="GenericAccountDirectories"/> class.
18+
/// </summary>
19+
/// <param name="rootPath">The root path relative to the file systems root path.</param>
20+
/// <param name="homePath">The home directory of the user relative to the <paramref name="rootPath"/>.</param>
21+
public GenericAccountDirectories(
22+
[CanBeNull] string rootPath,
23+
[CanBeNull] string homePath = null)
24+
{
25+
RootPath = rootPath.RemoveRoot();
26+
HomePath = homePath.RemoveRoot();
27+
}
28+
29+
/// <inheritdoc />
30+
public string RootPath { get; }
31+
32+
/// <inheritdoc />
33+
public string HomePath { get; }
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// <copyright file="RootPerUserAccountDirectoryQuery.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using System.IO;
6+
7+
using FubarDev.FtpServer.FileSystem;
8+
9+
using JetBrains.Annotations;
10+
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.Options;
13+
14+
namespace FubarDev.FtpServer.AccountManagement.Directories.RootPerUser
15+
{
16+
/// <summary>
17+
/// A single root directory per user.
18+
/// </summary>
19+
public class RootPerUserAccountDirectoryQuery : IAccountDirectoryQuery
20+
{
21+
[CanBeNull]
22+
private readonly ILogger<RootPerUserAccountDirectoryQuery> _logger;
23+
24+
[NotNull]
25+
private readonly string _anonymousRoot;
26+
27+
[NotNull]
28+
private readonly string _userRoot;
29+
30+
private readonly bool _anonymousRootPerEmail;
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="RootPerUserAccountDirectoryQuery"/> class.
34+
/// </summary>
35+
/// <param name="options">The options.</param>
36+
/// <param name="logger">The logger.</param>
37+
public RootPerUserAccountDirectoryQuery(
38+
[NotNull] IOptions<RootPerUserAccountDirectoryQueryOptions> options,
39+
[CanBeNull] ILogger<RootPerUserAccountDirectoryQuery> logger = null)
40+
{
41+
_logger = logger;
42+
_anonymousRoot = options.Value.AnonymousRootDirectory.RemoveRoot() ?? string.Empty;
43+
_userRoot = options.Value.UserRootDirectory.RemoveRoot() ?? string.Empty;
44+
_anonymousRootPerEmail = options.Value.AnonymousRootPerEmail;
45+
}
46+
47+
/// <inheritdoc />
48+
public IAccountDirectories GetDirectories(IAccountInformation accountInformation)
49+
{
50+
if (accountInformation.User is IAnonymousFtpUser anonymousFtpUser)
51+
{
52+
return GetAnonymousDirectories(anonymousFtpUser);
53+
}
54+
55+
var rootPath = Path.Combine(_userRoot, accountInformation.User.Name);
56+
return new GenericAccountDirectories(rootPath);
57+
}
58+
59+
private IAccountDirectories GetAnonymousDirectories(IAnonymousFtpUser ftpUser)
60+
{
61+
var rootPath = _anonymousRoot;
62+
if (_anonymousRootPerEmail)
63+
{
64+
if (string.IsNullOrEmpty(ftpUser.Email))
65+
{
66+
_logger?.LogWarning("Anonymous root per email is configured, but got anonymous user without email. This anonymous user will see the files of all other anonymous users!");
67+
}
68+
else
69+
{
70+
rootPath = Path.Combine(rootPath, ftpUser.Email);
71+
}
72+
}
73+
74+
return new GenericAccountDirectories(rootPath);
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// <copyright file="RootPerUserAccountDirectoryQueryOptions.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using System.ComponentModel;
6+
7+
using JetBrains.Annotations;
8+
9+
namespace FubarDev.FtpServer.AccountManagement.Directories.RootPerUser
10+
{
11+
/// <summary>
12+
/// Options for the <see cref="RootPerUserAccountDirectoryQuery"/>.
13+
/// </summary>
14+
public class RootPerUserAccountDirectoryQueryOptions
15+
{
16+
/// <summary>
17+
/// Gets or sets the normal authenticated users root directory.
18+
/// </summary>
19+
/// <remarks>
20+
/// This path is relative to the file systems root path.
21+
/// </remarks>
22+
[CanBeNull]
23+
public string UserRootDirectory { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the anonymous root directory.
27+
/// </summary>
28+
/// <remarks>
29+
/// Anonymous users will have the root <c>anonymous</c> if this
30+
/// property isn't set.
31+
/// This path is relative to the file systems root path.
32+
/// </remarks>
33+
[CanBeNull]
34+
[DefaultValue("anonymous")]
35+
public string AnonymousRootDirectory { get; set; }
36+
37+
/// <summary>
38+
/// Gets or sets a value indicating whether anonymous users should have their own (per-email) root directory.
39+
/// </summary>
40+
public bool AnonymousRootPerEmail { get; set; }
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// <copyright file="SingleRootWithoutHomeAccountDirectoryQuery.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using FubarDev.FtpServer.FileSystem;
6+
7+
using JetBrains.Annotations;
8+
9+
using Microsoft.Extensions.Options;
10+
11+
namespace FubarDev.FtpServer.AccountManagement.Directories.SingleRootWithoutHome
12+
{
13+
/// <summary>
14+
/// All users share the same root and none has a home directory.
15+
/// </summary>
16+
public class SingleRootWithoutHomeAccountDirectoryQuery : IAccountDirectoryQuery
17+
{
18+
[NotNull]
19+
private readonly GenericAccountDirectories _accountDirectories;
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="SingleRootWithoutHomeAccountDirectoryQuery"/> class.
23+
/// </summary>
24+
/// <param name="options">The options.</param>
25+
public SingleRootWithoutHomeAccountDirectoryQuery(
26+
IOptions<SingleRootWithoutHomeAccountDirectoryQueryOptions> options)
27+
{
28+
var opts = options.Value;
29+
_accountDirectories = new GenericAccountDirectories(opts.RootPath);
30+
}
31+
32+
/// <inheritdoc />
33+
public IAccountDirectories GetDirectories(IAccountInformation accountInformation)
34+
{
35+
return _accountDirectories;
36+
}
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// <copyright file="SingleRootWithoutHomeAccountDirectoryQueryOptions.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using JetBrains.Annotations;
6+
7+
namespace FubarDev.FtpServer.AccountManagement.Directories.SingleRootWithoutHome
8+
{
9+
/// <summary>
10+
/// Options for the <see cref="SingleRootWithoutHomeAccountDirectoryQuery"/>.
11+
/// </summary>
12+
public class SingleRootWithoutHomeAccountDirectoryQueryOptions
13+
{
14+
/// <summary>
15+
/// Gets or sets the root path.
16+
/// </summary>
17+
[CanBeNull]
18+
public string RootPath { get; set; }
19+
}
20+
}

0 commit comments

Comments
 (0)