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