Portable way to ask for password
This commit is contained in:
parent
4778c28898
commit
fafbbb8e3a
@ -13,10 +13,11 @@ namespace cpputils {
|
||||
|
||||
class Console {
|
||||
public:
|
||||
virtual ~Console() {}
|
||||
virtual ~Console() = default;
|
||||
virtual unsigned int ask(const std::string &question, const std::vector<std::string> &options) = 0;
|
||||
virtual bool askYesNo(const std::string &question, bool defaultValue) = 0; // NoninteractiveConsole will just return the default value without asking the user.
|
||||
virtual void print(const std::string &output) = 0;
|
||||
virtual std::string askPassword(const std::string &question) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "IOStreamConsole.h"
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include "DontEchoStdinToStdoutRAII.h"
|
||||
|
||||
using std::ostream;
|
||||
using std::istream;
|
||||
@ -91,8 +92,24 @@ bool IOStreamConsole::askYesNo(const string &question, bool /*defaultValue*/) {
|
||||
return _askForChoice("Your choice [y/n]: ", _parseYesNo());
|
||||
}
|
||||
|
||||
void IOStreamConsole::print(const std::string &output) {
|
||||
void IOStreamConsole::print(const string &output) {
|
||||
_output << output << std::flush;
|
||||
}
|
||||
|
||||
string IOStreamConsole::askPassword(const string &question) {
|
||||
DontEchoStdinToStdoutRAII _stdin_input_is_hidden_as_long_as_this_is_in_scope;
|
||||
|
||||
_output << question << std::flush;
|
||||
string result;
|
||||
std::getline(_input, result);
|
||||
_output << std::endl;
|
||||
|
||||
//Remove trailing newline
|
||||
if (result[result.size()-1] == '\n') {
|
||||
result.resize(result.size()-1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace cpputils {
|
||||
unsigned int ask(const std::string &question, const std::vector<std::string> &options) override;
|
||||
bool askYesNo(const std::string &question, bool defaultValue) override;
|
||||
void print(const std::string &output) override;
|
||||
std::string askPassword(const std::string &question) override;
|
||||
private:
|
||||
template<typename Return>
|
||||
Return _askForChoice(const std::string &question, std::function<boost::optional<Return> (const std::string&)> parse);
|
||||
|
@ -21,4 +21,8 @@ unsigned int NoninteractiveConsole::ask(const string &/*question*/, const vector
|
||||
throw std::logic_error("Tried to ask a multiple choice question in noninteractive mode");
|
||||
}
|
||||
|
||||
string NoninteractiveConsole::askPassword(const string &question) {
|
||||
return _baseConsole->askPassword(question);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace cpputils {
|
||||
unsigned int ask(const std::string &question, const std::vector<std::string> &options) override;
|
||||
bool askYesNo(const std::string &question, bool defaultValue) override;
|
||||
void print(const std::string &output) override;
|
||||
std::string askPassword(const std::string &question) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Console> _baseConsole;
|
||||
|
@ -124,34 +124,38 @@ namespace cryfs {
|
||||
return true;
|
||||
}
|
||||
|
||||
string Cli::_askPasswordForExistingFilesystem() {
|
||||
string password = _askPasswordFromStdin("Password: ");
|
||||
while (!_checkPassword(password)) {
|
||||
password = _askPasswordFromStdin("Password: ");
|
||||
}
|
||||
return password;
|
||||
function<string()> Cli::_askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console) {
|
||||
return [console] () {
|
||||
string password = console->askPassword("Password: ");
|
||||
while (!_checkPassword(password)) {
|
||||
password = console->askPassword("Password: ");
|
||||
}
|
||||
return password;
|
||||
};
|
||||
};
|
||||
|
||||
string Cli::_askPasswordForNewFilesystem() {
|
||||
string password;
|
||||
bool again = false;
|
||||
do {
|
||||
password = _askPasswordFromStdin("Password: ");
|
||||
if (!_checkPassword(password)) {
|
||||
again = true;
|
||||
continue;
|
||||
}
|
||||
if (!_confirmPassword(password)) {
|
||||
again = true;
|
||||
continue;
|
||||
}
|
||||
again = false;
|
||||
} while(again);
|
||||
return password;
|
||||
function<string()> Cli::_askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console) {
|
||||
return [console] () {
|
||||
string password;
|
||||
bool again = false;
|
||||
do {
|
||||
password = console->askPassword("Password: ");
|
||||
if (!_checkPassword(password)) {
|
||||
again = true;
|
||||
continue;
|
||||
}
|
||||
if (!_confirmPassword(console.get(), password)) {
|
||||
again = true;
|
||||
continue;
|
||||
}
|
||||
again = false;
|
||||
} while (again);
|
||||
return password;
|
||||
};
|
||||
}
|
||||
|
||||
bool Cli::_confirmPassword(const string &password) {
|
||||
string confirmPassword = _askPasswordFromStdin("Confirm Password: ");
|
||||
bool Cli::_confirmPassword(cpputils::Console* console, const string &password) {
|
||||
string confirmPassword = console->askPassword("Confirm Password: ");
|
||||
if (password != confirmPassword) {
|
||||
std::cout << "Passwords don't match" << std::endl;
|
||||
return false;
|
||||
@ -159,29 +163,15 @@ namespace cryfs {
|
||||
return true;
|
||||
}
|
||||
|
||||
string Cli::_askPasswordNoninteractive() {
|
||||
function<string()> Cli::_askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console) {
|
||||
//TODO Test
|
||||
string password = _askPasswordFromStdin("Password: ");
|
||||
if (!_checkPassword(password)) {
|
||||
throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
string Cli::_askPasswordFromStdin(const string &prompt) {
|
||||
DontEchoStdinToStdoutRAII _stdin_input_is_hidden_as_long_as_this_is_in_scope;
|
||||
|
||||
std::cout << prompt << std::flush;
|
||||
string result;
|
||||
std::getline(cin, result);
|
||||
std::cout << std::endl;
|
||||
|
||||
//Remove trailing newline
|
||||
if (result[result.size()-1] == '\n') {
|
||||
result.resize(result.size()-1);
|
||||
}
|
||||
|
||||
return result;
|
||||
return [console] () {
|
||||
string password = console->askPassword("Password: ");
|
||||
if (!_checkPassword(password)) {
|
||||
throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
|
||||
}
|
||||
return password;
|
||||
};
|
||||
}
|
||||
|
||||
bf::path Cli::_determineConfigFile(const ProgramOptions &options) {
|
||||
@ -218,13 +208,13 @@ namespace cryfs {
|
||||
optional<CryConfigLoader::ConfigLoadResult> Cli::_loadOrCreateConfigFile(bf::path configFilePath, LocalStateDir localStateDir, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem) {
|
||||
if (_noninteractive) {
|
||||
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
|
||||
&Cli::_askPasswordNoninteractive,
|
||||
&Cli::_askPasswordNoninteractive,
|
||||
Cli::_askPasswordNoninteractive(_console),
|
||||
Cli::_askPasswordNoninteractive(_console),
|
||||
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
|
||||
} else {
|
||||
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
|
||||
&Cli::_askPasswordForExistingFilesystem,
|
||||
&Cli::_askPasswordForNewFilesystem,
|
||||
Cli::_askPasswordForExistingFilesystem(_console),
|
||||
Cli::_askPasswordForNewFilesystem(_console),
|
||||
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,10 @@ namespace cryfs {
|
||||
void _checkConfigIntegrity(const boost::filesystem::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem);
|
||||
boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem);
|
||||
boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options);
|
||||
static std::string _askPasswordForExistingFilesystem();
|
||||
static std::string _askPasswordForNewFilesystem();
|
||||
static std::string _askPasswordNoninteractive();
|
||||
static std::string _askPasswordFromStdin(const std::string &prompt);
|
||||
static bool _confirmPassword(const std::string &password);
|
||||
static std::function<std::string()> _askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console);
|
||||
static std::function<std::string()> _askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console);
|
||||
static std::function<std::string()> _askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console);
|
||||
static bool _confirmPassword(cpputils::Console* console, const std::string &password);
|
||||
static bool _checkPassword(const std::string &password);
|
||||
void _showVersion(cpputils::unique_ref<cpputils::HttpClient> httpClient);
|
||||
void _initLogfile(const program_options::ProgramOptions &options);
|
||||
|
@ -27,6 +27,7 @@ set(SOURCES
|
||||
io/ConsoleTest_AskYesNo.cpp
|
||||
io/ConsoleTest_Print.cpp
|
||||
io/ConsoleTest_Ask.cpp
|
||||
io/ConsoleTest_AskPassword.cpp
|
||||
random/RandomIncludeTest.cpp
|
||||
lock/LockPoolIncludeTest.cpp
|
||||
lock/ConditionBarrierIncludeTest.cpp
|
||||
|
@ -23,6 +23,11 @@ public:
|
||||
return _console.askYesNo(question, true);
|
||||
});
|
||||
}
|
||||
std::future<std::string> askPassword(const std::string &question) {
|
||||
return std::async(std::launch::async, [this, question]() {
|
||||
return _console.askPassword(question);
|
||||
});
|
||||
}
|
||||
void print(const std::string &output) {
|
||||
_console.print(output);
|
||||
}
|
||||
@ -63,6 +68,10 @@ public:
|
||||
return _console.askYesNo(question);
|
||||
}
|
||||
|
||||
std::future<std::string> askPassword(const std::string &question) {
|
||||
return _console.askPassword(question);
|
||||
}
|
||||
|
||||
void print(const std::string &output) {
|
||||
_console.print(output);
|
||||
}
|
||||
|
22
test/cpp-utils/io/ConsoleTest_AskPassword.cpp
Normal file
22
test/cpp-utils/io/ConsoleTest_AskPassword.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "ConsoleTest.h"
|
||||
|
||||
using std::stringstream;
|
||||
using std::string;
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
|
||||
class ConsoleTest_AskPassword: public ConsoleTest {};
|
||||
|
||||
TEST_F(ConsoleTest_AskPassword, InputSomePassword) {
|
||||
auto chosen = askPassword("Please enter my password:");
|
||||
EXPECT_OUTPUT_LINE("Please enter my password", ':');
|
||||
sendInputLine("this is the password");
|
||||
EXPECT_EQ("this is the password", chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskPassword, InputEmptyPassword) {
|
||||
auto chosen = askPassword("Please enter my password:");
|
||||
EXPECT_OUTPUT_LINE("Please enter my password", ':');
|
||||
sendInputLine("");
|
||||
EXPECT_EQ("", chosen.get());
|
||||
}
|
@ -42,9 +42,8 @@ public:
|
||||
_args.push_back(arg);
|
||||
}
|
||||
auto &keyGenerator = cpputils::Random::PseudoRandom();
|
||||
// Write 2x 'pass\n' to stdin so Cryfs can read it as password (+ password confirmation prompt)
|
||||
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
|
||||
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
|
||||
ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass"));
|
||||
ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
|
||||
// Run Cryfs
|
||||
return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient());
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public:
|
||||
MOCK_METHOD1(print, void(const std::string&));
|
||||
MOCK_METHOD2(ask, unsigned int(const std::string&, const std::vector<std::string>&));
|
||||
MOCK_METHOD2(askYesNo, bool(const std::string&, bool));
|
||||
MOCK_METHOD1(askPassword, std::string(const std::string&));
|
||||
};
|
||||
|
||||
ACTION_P(ChooseCipher, cipherName) {
|
||||
|
Loading…
Reference in New Issue
Block a user