mirror of https://github.com/Icinga/icinga2.git
Remove ApiUser password_hash functionality
This affects and fixes - Windows reload - Config validation - RHEL 7.5 OpenSSL memory corruption - Hash algorithm, requested changes refs #6378 refs #6279 refs #6278
This commit is contained in:
parent
eb1f37905d
commit
2fd6709952
|
@ -112,7 +112,6 @@ Configuration Attributes:
|
||||||
Name | Type | Description
|
Name | Type | Description
|
||||||
--------------------------|-----------------------|----------------------------------
|
--------------------------|-----------------------|----------------------------------
|
||||||
password | String | **Optional.** Password string. Note: This attribute is hidden in API responses.
|
password | String | **Optional.** Password string. Note: This attribute is hidden in API responses.
|
||||||
password\_hash | String | **Optional.** A hashed password string in the form of /etc/shadow. Note: This attribute is hidden in API responses.
|
|
||||||
client\_cn | String | **Optional.** Client Common Name (CN).
|
client\_cn | String | **Optional.** Client Common Name (CN).
|
||||||
permissions | Array | **Required.** Array of permissions. Either as string or dictionary with the keys `permission` and `filter`. The latter must be specified as function.
|
permissions | Array | **Required.** Array of permissions. Either as string or dictionary with the keys `permission` and `filter`. The latter must be specified as function.
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ Usage:
|
||||||
|
|
||||||
Supported commands:
|
Supported commands:
|
||||||
* api setup (setup for API)
|
* api setup (setup for API)
|
||||||
* api user (API user creation helper)
|
|
||||||
* ca list (lists all certificate signing requests)
|
* ca list (lists all certificate signing requests)
|
||||||
* ca sign (signs an outstanding certificate request)
|
* ca sign (signs an outstanding certificate request)
|
||||||
* console (Icinga console)
|
* console (Icinga console)
|
||||||
|
|
|
@ -21,25 +21,6 @@ If you prefer to set up the API manually, you will have to perform the following
|
||||||
|
|
||||||
The next chapter provides a quick overview of how you can use the API.
|
The next chapter provides a quick overview of how you can use the API.
|
||||||
|
|
||||||
### Creating ApiUsers <a id="icinga2-api-creating-users"></a>
|
|
||||||
|
|
||||||
The CLI command `icinga2 api user` allows you to create an ApiUser object with a hashed password string, ready to be
|
|
||||||
added to your configuration. Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ icinga2 api user --user icingaweb2 --password icinga
|
|
||||||
object ApiUser "icingaweb2" {
|
|
||||||
password_hash ="$5$d5f1a17ea308acb6$9e9fd5d24a9373a16e8811765cc5a5939687faf9ef8ed496db6e7f1d0ae9b2a9"
|
|
||||||
// client_cn = ""
|
|
||||||
|
|
||||||
permissions = [ "*" ]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Optionally a salt can be provided with `--salt`, otherwise a random value will be used. When ApiUsers are stored this
|
|
||||||
way, even somebody able to read the configuration files won't be able to authenticate using this information. There is
|
|
||||||
no way to recover your password should you forget it, you'd need to create it anew.
|
|
||||||
|
|
||||||
## Introduction <a id="icinga2-api-introduction"></a>
|
## Introduction <a id="icinga2-api-introduction"></a>
|
||||||
|
|
||||||
The Icinga 2 API allows you to manage configuration objects
|
The Icinga 2 API allows you to manage configuration objects
|
||||||
|
|
|
@ -18,6 +18,11 @@ The CORS attributes `access_control_allow_credentials`, `access_control_allow_he
|
||||||
|
|
||||||
The `node setup` parameter `--master_host` was deprecated and replaced with `--parent_host`. This parameter is now optional to allow connection-less client setups similar to the `node wizard` CLI command. The `parent_zone` parameter has been added to modify the parent zone name e.g. for client-to-satellite setups.
|
The `node setup` parameter `--master_host` was deprecated and replaced with `--parent_host`. This parameter is now optional to allow connection-less client setups similar to the `node wizard` CLI command. The `parent_zone` parameter has been added to modify the parent zone name e.g. for client-to-satellite setups.
|
||||||
|
|
||||||
|
The `api user` command which was released in v2.8.2 turned out to cause huge problems with
|
||||||
|
configuration validation, windows restarts and OpenSSL versions. It is therefore removed in 2.9,
|
||||||
|
the `password_hash` attribute for the ApiUser object stays intact but has no effect. This is to ensure
|
||||||
|
that clients don't break on upgrade. We will revise this feature in future development iterations.
|
||||||
|
|
||||||
## Upgrading to v2.8.2 <a id="upgrading-to-2-8-2"></a>
|
## Upgrading to v2.8.2 <a id="upgrading-to-2-8-2"></a>
|
||||||
|
|
||||||
With version 2.8.2 the location of settings formerly found in `/etc/icinga2/init.conf` has changed. They are now
|
With version 2.8.2 the location of settings formerly found in `/etc/icinga2/init.conf` has changed. They are now
|
||||||
|
|
|
@ -784,34 +784,4 @@ std::string to_string(const errinfo_openssl_error& e)
|
||||||
return "[errinfo_openssl_error]" + tmp.str() + "\n";
|
return "[errinfo_openssl_error]" + tmp.str() + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComparePassword(const String& hash, const String& password, const String& salt)
|
|
||||||
{
|
|
||||||
String otherHash = PBKDF2_SHA256(password, salt, 1000);
|
|
||||||
VERIFY(otherHash.GetLength() == 64 && hash.GetLength() == 64);
|
|
||||||
|
|
||||||
const char *p1 = otherHash.CStr();
|
|
||||||
const char *p2 = hash.CStr();
|
|
||||||
|
|
||||||
/* By Novelocrat, https://stackoverflow.com/a/25374036 */
|
|
||||||
volatile char c = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 64; ++i)
|
|
||||||
c |= p1[i] ^ p2[i];
|
|
||||||
|
|
||||||
return (c == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns a String in the format $algorithm$salt$hash or returns an empty string in case of an error */
|
|
||||||
String CreateHashedPasswordString(const String& password, const String& salt, int algorithm)
|
|
||||||
{
|
|
||||||
// We currently only support SHA256
|
|
||||||
if (algorithm != 5)
|
|
||||||
return String();
|
|
||||||
|
|
||||||
if (salt.FindFirstOf('$') != String::NPos)
|
|
||||||
return String();
|
|
||||||
|
|
||||||
return String("$5$" + salt + "$" + PBKDF2_SHA256(password, salt, 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,6 @@ String SHA1(const String& s, bool binary = false);
|
||||||
String SHA256(const String& s);
|
String SHA256(const String& s);
|
||||||
String RandomString(int length);
|
String RandomString(int length);
|
||||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
||||||
bool ComparePassword(const String& hash, const String& password, const String& Salt);
|
|
||||||
String CreateHashedPasswordString(const String& password, const String& salt, int algorithm = 5);
|
|
||||||
|
|
||||||
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
set(cli_SOURCES
|
set(cli_SOURCES
|
||||||
i2-cli.hpp
|
i2-cli.hpp
|
||||||
apisetupcommand.cpp apisetupcommand.hpp
|
apisetupcommand.cpp apisetupcommand.hpp
|
||||||
apiusercommand.cpp apiusercommand.hpp
|
|
||||||
apisetuputility.cpp apisetuputility.hpp
|
apisetuputility.cpp apisetuputility.hpp
|
||||||
calistcommand.cpp calistcommand.hpp
|
calistcommand.cpp calistcommand.hpp
|
||||||
casigncommand.cpp casigncommand.hpp
|
casigncommand.cpp casigncommand.hpp
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Icinga 2 *
|
|
||||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
|
||||||
* *
|
|
||||||
* This program is free software; you can redistribute it and/or *
|
|
||||||
* modify it under the terms of the GNU General Public License *
|
|
||||||
* as published by the Free Software Foundation; either version 2 *
|
|
||||||
* of the License, or (at your option) any later version. *
|
|
||||||
* *
|
|
||||||
* This program is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with this program; if not, write to the Free Software Foundation *
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
#include "cli/apiusercommand.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/tlsutility.hpp"
|
|
||||||
#include "base/configwriter.hpp"
|
|
||||||
#include "remote/apiuser.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
namespace po = boost::program_options;
|
|
||||||
|
|
||||||
REGISTER_CLICOMMAND("api/user", ApiUserCommand);
|
|
||||||
|
|
||||||
String ApiUserCommand::GetDescription(void) const
|
|
||||||
{
|
|
||||||
return "Create a hashed user and password string for the Icinga 2 API";
|
|
||||||
}
|
|
||||||
|
|
||||||
String ApiUserCommand::GetShortDescription(void) const
|
|
||||||
{
|
|
||||||
return "API user creation helper";
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiUserCommand::InitParameters(boost::program_options::options_description& visibleDesc,
|
|
||||||
boost::program_options::options_description& hiddenDesc) const
|
|
||||||
{
|
|
||||||
visibleDesc.add_options()
|
|
||||||
("user", po::value<std::string>(), "API username")
|
|
||||||
("password", po::value<std::string>(), "Password in clear text")
|
|
||||||
("salt", po::value<std::string>(), "Optional salt (default: 8 random chars)")
|
|
||||||
("oneline", "Print only the password hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entry point for the "api user" CLI command.
|
|
||||||
*
|
|
||||||
* @returns An exit status.
|
|
||||||
*/
|
|
||||||
int ApiUserCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
|
|
||||||
{
|
|
||||||
String passwd, salt;
|
|
||||||
if (!vm.count("user") && !vm.count("oneline")) {
|
|
||||||
Log(LogCritical, "cli", "Username (--user) must be specified.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!vm.count("password")) {
|
|
||||||
Log(LogCritical, "cli", "Password (--password) must be specified.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
passwd = vm["password"].as<std::string>();
|
|
||||||
salt = vm.count("salt") ? String(vm["salt"].as<std::string>()) : RandomString(8);
|
|
||||||
|
|
||||||
if (salt.FindFirstOf('$') != String::NPos) {
|
|
||||||
Log(LogCritical, "cli", "Salt (--salt) may not contain '$'");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String hashedPassword = CreateHashedPasswordString(passwd, salt, 5);
|
|
||||||
if (hashedPassword == String()) {
|
|
||||||
Log(LogCritical, "cli") << "Failed to hash password \"" << passwd << "\" with salt \"" << salt << "\"";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("oneline"))
|
|
||||||
std::cout << hashedPassword << std::endl;
|
|
||||||
else {
|
|
||||||
std::cout << "object ApiUser ";
|
|
||||||
|
|
||||||
ConfigWriter::EmitString(std::cout, vm["user"].as<std::string>());
|
|
||||||
|
|
||||||
std::cout << "{\n"
|
|
||||||
<< " password_hash = ";
|
|
||||||
|
|
||||||
ConfigWriter::EmitString(std::cout, hashedPassword);
|
|
||||||
|
|
||||||
std::cout << "\n"
|
|
||||||
<< " // client_cn = \"\"\n"
|
|
||||||
<< "\n"
|
|
||||||
<< " permissions = [ \"*\" ]\n"
|
|
||||||
<< "}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Icinga 2 *
|
|
||||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
|
||||||
* *
|
|
||||||
* This program is free software; you can redistribute it and/or *
|
|
||||||
* modify it under the terms of the GNU General Public License *
|
|
||||||
* as published by the Free Software Foundation; either version 2 *
|
|
||||||
* of the License, or (at your option) any later version. *
|
|
||||||
* *
|
|
||||||
* This program is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with this program; if not, write to the Free Software Foundation *
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
#ifndef APIUSERCOMMAND_H
|
|
||||||
#define APIUSERCOMMAND_H
|
|
||||||
|
|
||||||
#include "cli/clicommand.hpp"
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The "api user" command.
|
|
||||||
*
|
|
||||||
* @ingroup cli
|
|
||||||
*/
|
|
||||||
class ApiUserCommand : public CLICommand
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DECLARE_PTR_TYPEDEFS(ApiUserCommand);
|
|
||||||
|
|
||||||
virtual String GetDescription(void) const override;
|
|
||||||
virtual String GetShortDescription(void) const override;
|
|
||||||
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
|
|
||||||
boost::program_options::options_description& hiddenDesc) const override;
|
|
||||||
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* APIUSERCOMMAND_H */
|
|
|
@ -27,18 +27,6 @@ using namespace icinga;
|
||||||
|
|
||||||
REGISTER_TYPE(ApiUser);
|
REGISTER_TYPE(ApiUser);
|
||||||
|
|
||||||
void ApiUser::OnConfigLoaded(void)
|
|
||||||
{
|
|
||||||
ObjectImpl<ApiUser>::OnConfigLoaded();
|
|
||||||
|
|
||||||
if (GetPasswordHash().IsEmpty()) {
|
|
||||||
String hashedPassword = CreateHashedPasswordString(GetPassword(), RandomString(8), 5);
|
|
||||||
VERIFY(hashedPassword != String());
|
|
||||||
SetPasswordHash(hashedPassword);
|
|
||||||
SetPassword("********");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiUser::Ptr ApiUser::GetByClientCN(const String& cn)
|
ApiUser::Ptr ApiUser::GetByClientCN(const String& cn)
|
||||||
{
|
{
|
||||||
for (const ApiUser::Ptr& user : ConfigType::GetObjectsByType<ApiUser>()) {
|
for (const ApiUser::Ptr& user : ConfigType::GetObjectsByType<ApiUser>()) {
|
||||||
|
@ -75,31 +63,9 @@ ApiUser::Ptr ApiUser::GetByAuthHeader(const String& auth_header)
|
||||||
*/
|
*/
|
||||||
if (!user || password.IsEmpty())
|
if (!user || password.IsEmpty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
else if (user && user->GetPassword() != password) {
|
else if (user && user->GetPassword() != password)
|
||||||
Dictionary::Ptr passwordDict = user->GetPasswordDict();
|
|
||||||
if (!passwordDict || !ComparePassword(passwordDict->Get("password"), password, passwordDict->Get("salt")))
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary::Ptr ApiUser::GetPasswordDict(void) const
|
|
||||||
{
|
|
||||||
String password = GetPasswordHash();
|
|
||||||
if (password.IsEmpty() || password[0] != '$')
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
String::SizeType saltBegin = password.FindFirstOf('$', 1);
|
|
||||||
String::SizeType passwordBegin = password.FindFirstOf('$', saltBegin+1);
|
|
||||||
|
|
||||||
if (saltBegin == String::NPos || saltBegin == 1 || passwordBegin == String::NPos)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
Dictionary::Ptr passwordDict = new Dictionary();
|
|
||||||
passwordDict->Set("algorithm", password.SubStr(1, saltBegin - 1));
|
|
||||||
passwordDict->Set("salt", password.SubStr(saltBegin + 1, passwordBegin - saltBegin - 1));
|
|
||||||
passwordDict->Set("password", password.SubStr(passwordBegin + 1));
|
|
||||||
|
|
||||||
return passwordDict;
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,12 +35,8 @@ public:
|
||||||
DECLARE_OBJECT(ApiUser);
|
DECLARE_OBJECT(ApiUser);
|
||||||
DECLARE_OBJECTNAME(ApiUser);
|
DECLARE_OBJECTNAME(ApiUser);
|
||||||
|
|
||||||
virtual void OnConfigLoaded(void) override;
|
|
||||||
|
|
||||||
static ApiUser::Ptr GetByClientCN(const String& cn);
|
static ApiUser::Ptr GetByClientCN(const String& cn);
|
||||||
static ApiUser::Ptr GetByAuthHeader(const String& auth_header);
|
static ApiUser::Ptr GetByAuthHeader(const String& auth_header);
|
||||||
|
|
||||||
Dictionary::Ptr GetPasswordDict(void) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ApiUser : ConfigObject
|
||||||
{
|
{
|
||||||
/* No show config */
|
/* No show config */
|
||||||
[no_user_view, no_user_modify] String password;
|
[no_user_view, no_user_modify] String password;
|
||||||
[config, no_user_view] String password_hash;
|
[deprecated, config, no_user_view] String password_hash;
|
||||||
[config] String client_cn (ClientCN);
|
[config] String client_cn (ClientCN);
|
||||||
[config] array(Value) permissions;
|
[config] array(Value) permissions;
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,7 +43,6 @@ set(base_test_SOURCES
|
||||||
icinga-notification.cpp
|
icinga-notification.cpp
|
||||||
icinga-perfdata.cpp
|
icinga-perfdata.cpp
|
||||||
remote-url.cpp
|
remote-url.cpp
|
||||||
remote-user.cpp
|
|
||||||
${base_OBJS}
|
${base_OBJS}
|
||||||
$<TARGET_OBJECTS:config>
|
$<TARGET_OBJECTS:config>
|
||||||
$<TARGET_OBJECTS:remote>
|
$<TARGET_OBJECTS:remote>
|
||||||
|
@ -142,7 +141,6 @@ add_boost_test(base
|
||||||
remote_url/get_and_set
|
remote_url/get_and_set
|
||||||
remote_url/format
|
remote_url/format
|
||||||
remote_url/illegal_legal_strings
|
remote_url/illegal_legal_strings
|
||||||
api_user/password
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ICINGA2_WITH_LIVESTATUS)
|
if(ICINGA2_WITH_LIVESTATUS)
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Icinga 2 *
|
|
||||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
|
||||||
* *
|
|
||||||
* This program is free software; you can redistribute it and/or *
|
|
||||||
* modify it under the terms of the GNU General Public License *
|
|
||||||
* as published by the Free Software Foundation; either version 2 *
|
|
||||||
* of the License, or (at your option) any later version. *
|
|
||||||
* *
|
|
||||||
* This program is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with this program; if not, write to the Free Software Foundation *
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
#include "remote/apiuser.hpp"
|
|
||||||
#include "base/tlsutility.hpp"
|
|
||||||
#include <BoostTestTargetConfig.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(api_user)
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(password)
|
|
||||||
{
|
|
||||||
ApiUser::Ptr user = new ApiUser();
|
|
||||||
String passwd = RandomString(16);
|
|
||||||
String salt = RandomString(8);
|
|
||||||
user->SetPasswordHash(CreateHashedPasswordString(passwd, salt));
|
|
||||||
user->OnConfigLoaded();
|
|
||||||
user->OnAllConfigLoaded();
|
|
||||||
user->Start();
|
|
||||||
|
|
||||||
BOOST_CHECK(user->GetPasswordHash() != passwd);
|
|
||||||
|
|
||||||
Dictionary::Ptr passwdd = user->GetPasswordDict();
|
|
||||||
|
|
||||||
BOOST_CHECK(passwdd);
|
|
||||||
BOOST_CHECK(passwdd->Get("salt") == salt);
|
|
||||||
BOOST_CHECK(ComparePassword(passwdd->Get("password"), passwd, salt));
|
|
||||||
BOOST_CHECK(!ComparePassword(passwdd->Get("password"), "wrong password uwu!", salt));
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
|
Loading…
Reference in New Issue