Implement access control enhancements in ChatRoom and related classes
- Introduced a new overloaded method `accessAllowed` in `ChatRoom` to include user age verification for adult rooms. - Added `falukant_user_id` method in `ChatUser` to retrieve user ID for age checks. - Implemented `getUserAge` method in `ChatUser` to fetch and decrypt user birthdate from the database for age validation. - Updated `roomAllowed` methods in `Server` and `SSLServer` to utilize the new access control logic, ensuring proper user context is considered. - Enhanced debugging output for better traceability during access checks.
This commit is contained in:
@@ -559,7 +559,40 @@ namespace Yc
|
||||
|
||||
bool ChatRoom::accessAllowed(std::string userName, std::string password)
|
||||
{
|
||||
return (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end());
|
||||
return accessAllowed(userName, password, nullptr);
|
||||
}
|
||||
|
||||
bool ChatRoom::accessAllowed(std::string userName, std::string password, std::shared_ptr<ChatUser> user)
|
||||
{
|
||||
// Basis-Zugriffsprüfung (Passwort, erlaubte User)
|
||||
bool basicAccess = (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end());
|
||||
|
||||
if (!basicAccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Altersprüfung: Wenn min_age >= 18 (Adult-Mode), muss User mindestens 18 sein
|
||||
int minAge = _room.min_age();
|
||||
if (minAge >= 18 && _database && user) {
|
||||
int falukantUserId = user->falukant_user_id();
|
||||
if (falukantUserId > 0) {
|
||||
int userAge = ChatUser::getUserAge(_database, falukantUserId);
|
||||
if (userAge > 0 && userAge < 18) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] ChatRoom: User " << userName << " (age " << userAge << ") denied access to adult room (min_age: " << minAge << ")" << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Wenn kein falukant_user_id vorhanden, verweigere Zugang zu Adult-Räumen
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] ChatRoom: User " << userName << " (no falukant_user_id) denied access to adult room (min_age: " << minAge << ")" << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatRoom::userIsInRoom(std::string userName)
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Yc
|
||||
void sendToAllUsers(ChatUser::MsgType type, Json::Value message);
|
||||
unsigned int addDice(std::shared_ptr<ChatUser> user, int diceValue);
|
||||
bool accessAllowed(std::string userName, std::string password);
|
||||
bool accessAllowed(std::string userName, std::string password, std::shared_ptr<ChatUser> user);
|
||||
bool userIsInRoom(std::string userName);
|
||||
std::shared_ptr<ChatUser> findUserByName(std::string userName);
|
||||
std::shared_ptr<ChatUser> findUserByToken(std::string token);
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <netinet/tcp.h>
|
||||
#include <sstream>
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Yc
|
||||
{
|
||||
@@ -446,6 +448,11 @@ namespace Yc
|
||||
return _token;
|
||||
}
|
||||
|
||||
int ChatUser::falukant_user_id() const
|
||||
{
|
||||
return _user.falukant_user_id();
|
||||
}
|
||||
|
||||
bool ChatUser::validateToken(std::string token)
|
||||
{
|
||||
return (token == _token);
|
||||
@@ -461,6 +468,73 @@ namespace Yc
|
||||
return _wsi != nullptr;
|
||||
}
|
||||
|
||||
int ChatUser::getUserAge(std::shared_ptr<Database> database, int falukantUserId)
|
||||
{
|
||||
if (!database || falukantUserId <= 0) {
|
||||
return 0; // Kein Alter verfügbar
|
||||
}
|
||||
|
||||
try {
|
||||
// Hole verschlüsseltes Geburtsdatum aus user_param mit JOIN auf type.user_param
|
||||
std::string query = R"(
|
||||
SELECT up.value
|
||||
FROM community.user_param up
|
||||
JOIN "type".user_param tp ON up.param_type_id = tp.id
|
||||
WHERE up.user_id = )" + std::to_string(falukantUserId) + R"(
|
||||
AND tp.description = 'birthdate'
|
||||
LIMIT 1;
|
||||
)";
|
||||
auto result = database->exec(query);
|
||||
|
||||
if (result.size() > 0 && !result[0]["value"].is_null()) {
|
||||
std::string encryptedValue = result[0]["value"].as<std::string>();
|
||||
|
||||
// Entschlüssele den Wert
|
||||
std::string decryptedValue = Yc::Lib::Tools::decryptAES256ECB(encryptedValue);
|
||||
|
||||
if (decryptedValue.empty()) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cerr << "[Debug] ChatUser: Failed to decrypt birthdate for user ID " << falukantUserId << std::endl;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse Datum (Format: YYYY-MM-DD)
|
||||
std::tm tm = {};
|
||||
std::istringstream ss(decryptedValue);
|
||||
ss >> std::get_time(&tm, "%Y-%m-%d");
|
||||
|
||||
if (ss.fail()) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cerr << "[Debug] ChatUser: Failed to parse birthdate: " << decryptedValue << std::endl;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Berechne Alter
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* nowTm = std::localtime(&now);
|
||||
|
||||
int age = nowTm->tm_year + 1900 - (tm.tm_year + 1900);
|
||||
if (nowTm->tm_mon < tm.tm_mon ||
|
||||
(nowTm->tm_mon == tm.tm_mon && nowTm->tm_mday < tm.tm_mday)) {
|
||||
age--;
|
||||
}
|
||||
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] ChatUser: Loaded age from database for user ID " << falukantUserId << ": " << age << std::endl;
|
||||
#endif
|
||||
return age;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cerr << "[Debug] ChatUser: Error loading age from database: " << e.what() << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0; // Kein Alter verfügbar
|
||||
}
|
||||
|
||||
std::string ChatUser::loadColorFromDatabase(std::shared_ptr<Database> database, std::string userName)
|
||||
{
|
||||
if (!database) {
|
||||
|
||||
@@ -41,10 +41,12 @@ namespace Yc
|
||||
~ChatUser();
|
||||
std::string name() const;
|
||||
std::string getToken() const;
|
||||
int falukant_user_id() const;
|
||||
bool validateToken(std::string token);
|
||||
bool isUser(std::shared_ptr<ChatUser> toValidate);
|
||||
bool isWebSocket() const;
|
||||
static std::string loadColorFromDatabase(std::shared_ptr<Database> database, std::string userName);
|
||||
static int getUserAge(std::shared_ptr<Database> database, int falukantUserId);
|
||||
void sendMsg(MsgType type, std::string message, std::string userName, std::string color);
|
||||
void sendMsg(MsgType type, const char *message, std::string userName, std::string color);
|
||||
void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color);
|
||||
|
||||
@@ -224,6 +224,10 @@ namespace Yc {
|
||||
}
|
||||
|
||||
bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){
|
||||
return roomAllowed(roomName, userName, password, nullptr);
|
||||
}
|
||||
|
||||
bool Server::roomAllowed(std::string roomName, std::string userName, std::string password, std::shared_ptr<ChatUser> user){
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl;
|
||||
std::cout << "[Debug] Available rooms: ";
|
||||
@@ -237,7 +241,7 @@ namespace Yc {
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl;
|
||||
#endif
|
||||
if (room->name() == roomName && room->accessAllowed(userName, password)) {
|
||||
if (room->name() == roomName && room->accessAllowed(userName, password, user)) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] Room found and access allowed!" << std::endl;
|
||||
#endif
|
||||
@@ -251,7 +255,7 @@ namespace Yc {
|
||||
}
|
||||
|
||||
bool Server::changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password) {
|
||||
if (!roomAllowed(newRoom, user->name(), password)) {
|
||||
if (!roomAllowed(newRoom, user->name(), password, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Yc {
|
||||
std::vector<std::string> roomList();
|
||||
Json::Value jsonRoomList();
|
||||
bool roomAllowed(std::string roomName, std::string userName, std::string password);
|
||||
bool roomAllowed(std::string roomName, std::string userName, std::string password, std::shared_ptr<ChatUser> user);
|
||||
bool changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password);
|
||||
int _socket;
|
||||
std::shared_ptr<Yc::Lib::Config> _config;
|
||||
|
||||
@@ -892,8 +892,12 @@ std::vector<std::string> SSLServer::roomList() {
|
||||
}
|
||||
|
||||
bool SSLServer::roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password) {
|
||||
return roomAllowed(roomName, userName, password, nullptr);
|
||||
}
|
||||
|
||||
bool SSLServer::roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password, std::shared_ptr<ChatUser> user) {
|
||||
for (auto &room: _rooms) {
|
||||
if (room->name() == roomName && room->accessAllowed(userName, password)) {
|
||||
if (room->name() == roomName && room->accessAllowed(userName, password, user)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -901,7 +905,7 @@ bool SSLServer::roomAllowed(const std::string& roomName, const std::string& user
|
||||
}
|
||||
|
||||
bool SSLServer::changeRoom(std::shared_ptr<ChatUser> user, const std::string& newRoom, const std::string& password) {
|
||||
if (!roomAllowed(newRoom, user->name(), password)) {
|
||||
if (!roomAllowed(newRoom, user->name(), password, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
// Room management
|
||||
std::vector<std::string> roomList();
|
||||
bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password);
|
||||
bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password, std::shared_ptr<ChatUser> user);
|
||||
bool changeRoom(std::shared_ptr<ChatUser> user, const std::string& newRoom, const std::string& password);
|
||||
|
||||
// User management
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/err.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace Yc {
|
||||
namespace Lib {
|
||||
@@ -29,5 +37,84 @@ namespace Yc {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Tools::decryptAES256ECB(const std::string& encryptedHex) {
|
||||
try {
|
||||
// Lade SECRET_KEY aus Umgebungsvariable
|
||||
const char* secretEnv = std::getenv("SECRET_KEY");
|
||||
std::string secret = secretEnv ? secretEnv : "DEV_FALLBACK_SECRET";
|
||||
|
||||
// Generiere 32-Byte Schlüssel mit scrypt (wie in Node.js: scryptSync(secret, 'salt', 32))
|
||||
unsigned char key[32];
|
||||
const char* salt = "salt";
|
||||
|
||||
if (EVP_PBE_scrypt(secret.c_str(), secret.length(),
|
||||
(const unsigned char*)salt, strlen(salt),
|
||||
16384, 8, 1, 0, // N=16384, r=8, p=1, maxmem=0 (unbegrenzt)
|
||||
key, 32) != 1) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cerr << "[Debug] Tools: Error generating scrypt key" << std::endl;
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
// Konvertiere Hex-String zu Bytes
|
||||
std::vector<unsigned char> encryptedBytes;
|
||||
for (size_t i = 0; i < encryptedHex.length(); i += 2) {
|
||||
if (i + 1 >= encryptedHex.length()) break;
|
||||
std::string byteStr = encryptedHex.substr(i, 2);
|
||||
unsigned char byte = static_cast<unsigned char>(std::stoul(byteStr, nullptr, 16));
|
||||
encryptedBytes.push_back(byte);
|
||||
}
|
||||
|
||||
if (encryptedBytes.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Entschlüssele mit AES-256-ECB (ECB verwendet keinen IV, daher nullptr)
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_ecb(), nullptr, key, nullptr) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Padding aktivieren (PKCS7)
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
std::vector<unsigned char> decryptedBytes(encryptedBytes.size() + EVP_CIPHER_block_size(EVP_aes_256_ecb()));
|
||||
int outlen = 0;
|
||||
int finallen = 0;
|
||||
|
||||
if (EVP_DecryptUpdate(ctx, decryptedBytes.data(), &outlen,
|
||||
encryptedBytes.data(), encryptedBytes.size()) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx, decryptedBytes.data() + outlen, &finallen) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
// Kombiniere Output
|
||||
int totalLen = outlen + finallen;
|
||||
if (totalLen > 0 && totalLen <= static_cast<int>(decryptedBytes.size())) {
|
||||
return std::string(reinterpret_cast<const char*>(decryptedBytes.data()), totalLen);
|
||||
}
|
||||
|
||||
return "";
|
||||
} catch (const std::exception& e) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cerr << "[Debug] Tools: Error decrypting: " << e.what() << std::endl;
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Lib
|
||||
} // namespace Yp
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Yc {
|
||||
public:
|
||||
Tools();
|
||||
static std::string generateRandomString(size_t length = 16);
|
||||
static std::string decryptAES256ECB(const std::string& encryptedHex);
|
||||
};
|
||||
|
||||
} // namespace Lib
|
||||
|
||||
Reference in New Issue
Block a user