FubarDevelopment / FtpServer

Portable FTP server written in .NET
http://fubardevelopment.github.io/FtpServer/
MIT License
477 stars 163 forks source link

MembershipProvider breaks the supporting of IFtpUser on UseRootPerUser #89

Closed indication closed 4 years ago

indication commented 4 years ago

Overview

MembershipProvider returns MemberValidationResult with IFtpUser, the home directory name is anonymous , not on IFtpUser.Name .

Upgrade from 2.2.1.0 to 3.1.1.0

I tried to upgrade FubarDev.FtpServer from 2.2.1.0 to 3.1.1.0.

Following code is worked on 2.2.1.0, 3.1.1.0 is error.

class FtpUser : IFtpUser
{
    public string Name { get; set; }
    public FtpUser(string name)
    {
        Name = name;
    }

    public bool IsInGroup(string groupName)
    {
        return false;
    }
}
class MembershipProvider : IMembershipProvider
{
    private readonly Dictionary<string, string> Users = new Dictionary<string, string>();
    public void AddUser(string name, string pass)
    {
        Users.Add(name, pass);
    }
    public MemberValidationResult ValidateUser(string username, string password)
    {
        if (Users.ContainsKey(username) && string.Equals(password, Users[username]))
        {
            return new MemberValidationResult(MemberValidationStatus.AuthenticatedUser, new FtpUser(username));
        } else
            return new MemberValidationResult(MemberValidationStatus.InvalidLogin);
    }
}

/////////////////////////////

var services = new ServiceCollection();
Membership = new MembershipProvider();
services.Configure<DotNetFileSystemOptions>((opt) =>
{
    opt.UseUserIdAsSubFolder = true;
    opt.RootPath = TemporaryPath;
});
services.AddFtpServer(opt =>
{
    opt.UseDotNetFileSystem();
});
services.AddScoped<IMembershipProvider>((service) => membership);
var server = services.BuildServiceProvider();
server.GetRequiredService<IFtpServer>().Start();

After upgrade, it would be compiled on following, but not works: Client can connect to FTP Server, authentication is succeed, but it treat as home directory as anonymous.

// snipped the class of IFtpUser

class MembershipProvider : IMembershipProvider
{
    private readonly Dictionary<string, string> Users = new Dictionary<string, string>();
    public void AddUser(string name, string pass)
    {
        Users.Add(name, pass);
    }
    public MemberValidationResult ValidateUser(string username, string password)
    {
        if (Users.ContainsKey(username) && string.Equals(password, Users[username]))
        {
            return new MemberValidationResult(MemberValidationStatus.AuthenticatedUser, new FtpUser(username));
        } else
            return new MemberValidationResult(MemberValidationStatus.InvalidLogin);
    }

    public Task<MemberValidationResult> ValidateUserAsync(string username, string password)
    {
        return Task.Factory.StartNew(() => ValidateUser(username, password));
    }
}

/////////////////////////////

var services = new ServiceCollection();
Membership = new MembershipProvider();
services.Configure<DotNetFileSystemOptions>((opt) =>
{
    opt.RootPath = TemporaryPath;
});
services.AddFtpServer(opt =>
{
    opt.UseRootPerUser();
    opt.UseDotNetFileSystem();
});
services.AddScoped<IMembershipProvider>((service) => membership);
var server = services.BuildServiceProvider();
server.GetRequiredService<IFtpServer>().StartAsync(new CancellationToken()).Wait();

To solve the home directory name

Use ClaimsPrincipal instead of 'IFtpUser' on MemberValidationResult.

class MembershipProvider : IMembershipProvider
{
    private readonly Dictionary<string, string> Users = new Dictionary<string, string>();
    public void AddUser(string name, string pass)
    {
        Users.Add(name, pass);
    }
    public MemberValidationResult ValidateUser(string username, string password)
    {
        if (Users.ContainsKey(username) && string.Equals(password, Users[username]))
        {
            var claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.Name, username));
            claims.Add(new Claim(ClaimTypes.NameIdentifier, username));
            ClaimsIdentity userIdentity = new ClaimsIdentity(claims, "Identity.Application");
            ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
            return new MemberValidationResult(MemberValidationStatus.AuthenticatedUser, principal);
        } else
            return new MemberValidationResult(MemberValidationStatus.InvalidLogin);
    }

    public Task<MemberValidationResult> ValidateUserAsync(string username, string password)
    {
        return Task.Factory.StartNew(() => ValidateUser(username, password));
    }
}

Finally

I do not ask you to fix this because there is solution. This issue is a report. (I may wrong...) Thank you for providing very useful library.