Newer
Older
Sunfish / src / Sunfish.Cli / UserCommands.cs
@Ivan Dominguez Ivan Dominguez 21 hours ago 5 KB 3.0
using System.Text;
using Sunfish.Auth;
using Sunfish.Config;

namespace Sunfish.Cli;

/// <summary>sunfish-cli user add|passwd|del|list: usuarios del fichero de configuracion.</summary>
internal static class UserCommands
{
    public static int Run(string[] args)
    {
        string? configPath = null;
        var rest = new List<string>();
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i] is "-c" or "--config" && i + 1 < args.Length)
                configPath = args[++i];
            else if (args[i] == "--lang" && i + 1 < args.Length)
                Strings.Force(args[++i]);
            else
                rest.Add(args[i]);
        }
        if (rest.Count == 0 || (rest[0] != "list" && rest.Count != 2))
        {
            Console.Error.WriteLine(Texts.UserUsage);
            return 1;
        }

        string path = ConfigLocator.Find(configPath) ?? ConfigLocator.PortablePath;
        var config = File.Exists(path) ? SunfishConfig.Load(path) : new SunfishConfig { FilePath = path };
        string verb = rest[0];
        string name = rest.Count > 1 ? rest[1] : "";
        var user = config.Users.FirstOrDefault(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

        switch (verb)
        {
            case "list":
                if (config.Users.Count == 0)
                    Console.WriteLine(Texts.NoUsers);
                foreach (var u in config.Users)
                {
                    var shares = config.Shares.Where(s => s.Users.Contains(u.Name, StringComparer.OrdinalIgnoreCase)).Select(s => s.Name);
                    Console.WriteLine($"{u.Name}   ({Texts.UserShares(string.Join(", ", shares))})");
                }
                return 0;

            case "add":
                if (!IsValidName(name))
                {
                    Console.Error.WriteLine(Texts.BadUserName(name));
                    return 1;
                }
                if (user is not null)
                {
                    Console.Error.WriteLine(Texts.UserExists(name));
                    return 1;
                }
                string? password = AskNewPassword();
                if (password is null)
                    return 1;
                config.Users.Add(new UserConfig { Name = name, PasswordHash = Passwords.Hash(password) });
                config.Save(path);
                Console.WriteLine(Texts.UserSaved(name, path));
                Console.WriteLine(Texts.GrantHint);
                return 0;

            case "passwd":
                if (user is null)
                {
                    Console.Error.WriteLine(Texts.UserMissing(name));
                    return 1;
                }
                string? changed = AskNewPassword();
                if (changed is null)
                    return 1;
                user.PasswordHash = Passwords.Hash(changed);
                config.Save(path);
                Console.WriteLine(Texts.UserSaved(user.Name, path));
                return 0;

            case "del":
                if (user is null)
                {
                    Console.Error.WriteLine(Texts.UserMissing(name));
                    return 1;
                }
                config.Users.Remove(user);
                foreach (var share in config.Shares)
                    share.Users.RemoveAll(u => u.Equals(user.Name, StringComparison.OrdinalIgnoreCase));
                config.Save(path);
                Console.WriteLine(Texts.UserDeleted(user.Name));
                return 0;

            default:
                Console.Error.WriteLine(Texts.UserUsage);
                return 1;
        }
    }

    /// <summary>Sin espacios, comas ni ':' (separa usuario y contrasena en HTTP Basic).</summary>
    internal static bool IsValidName(string name) =>
        name.Length is > 0 and <= 64 && name.All(c => c > ' ' && c != ',' && c != ':' && c != '[' && c != ']' && c != 0x7F);

    internal static string? AskNewPassword()
    {
        string first = ReadPassword(Texts.PasswordPrompt);
        if (first.Length == 0)
        {
            Console.Error.WriteLine(Texts.PasswordEmpty);
            return null;
        }
        if (!Console.IsInputRedirected && ReadPassword(Texts.PasswordRepeat) != first)
        {
            Console.Error.WriteLine(Texts.PasswordMismatch);
            return null;
        }
        return first;
    }

    /// <summary>Lee sin eco en un terminal; con la entrada redirigida, una linea.</summary>
    private static string ReadPassword(string prompt)
    {
        Console.Error.Write(prompt);
        if (Console.IsInputRedirected)
        {
            string line = Console.ReadLine() ?? "";
            Console.Error.WriteLine();
            return line;
        }
        var sb = new StringBuilder();
        while (true)
        {
            var key = Console.ReadKey(intercept: true);
            if (key.Key == ConsoleKey.Enter)
                break;
            if (key.Key == ConsoleKey.Backspace)
            {
                if (sb.Length > 0)
                    sb.Length--;
            }
            else if (!char.IsControl(key.KeyChar))
            {
                sb.Append(key.KeyChar);
            }
        }
        Console.Error.WriteLine();
        return sb.ToString();
    }
}