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:
Torsten Schulz (local)
2025-12-18 14:10:50 +01:00
parent 0c18a97ed7
commit 016de9e5cf
10 changed files with 213 additions and 5 deletions

View File

@@ -559,7 +559,40 @@ namespace Yc
bool ChatRoom::accessAllowed(std::string userName, std::string password) 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) bool ChatRoom::userIsInRoom(std::string userName)

View File

@@ -60,6 +60,7 @@ namespace Yc
void sendToAllUsers(ChatUser::MsgType type, Json::Value message); void sendToAllUsers(ChatUser::MsgType type, Json::Value message);
unsigned int addDice(std::shared_ptr<ChatUser> user, int diceValue); 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);
bool accessAllowed(std::string userName, std::string password, std::shared_ptr<ChatUser> user);
bool userIsInRoom(std::string userName); bool userIsInRoom(std::string userName);
std::shared_ptr<ChatUser> findUserByName(std::string userName); std::shared_ptr<ChatUser> findUserByName(std::string userName);
std::shared_ptr<ChatUser> findUserByToken(std::string token); std::shared_ptr<ChatUser> findUserByToken(std::string token);

View File

@@ -16,6 +16,8 @@
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <sstream> #include <sstream>
#include <cctype> #include <cctype>
#include <ctime>
#include <iomanip>
namespace Yc namespace Yc
{ {
@@ -446,6 +448,11 @@ namespace Yc
return _token; return _token;
} }
int ChatUser::falukant_user_id() const
{
return _user.falukant_user_id();
}
bool ChatUser::validateToken(std::string token) bool ChatUser::validateToken(std::string token)
{ {
return (token == _token); return (token == _token);
@@ -461,6 +468,73 @@ namespace Yc
return _wsi != nullptr; 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) std::string ChatUser::loadColorFromDatabase(std::shared_ptr<Database> database, std::string userName)
{ {
if (!database) { if (!database) {

View File

@@ -41,10 +41,12 @@ namespace Yc
~ChatUser(); ~ChatUser();
std::string name() const; std::string name() const;
std::string getToken() const; std::string getToken() const;
int falukant_user_id() const;
bool validateToken(std::string token); bool validateToken(std::string token);
bool isUser(std::shared_ptr<ChatUser> toValidate); bool isUser(std::shared_ptr<ChatUser> toValidate);
bool isWebSocket() const; bool isWebSocket() const;
static std::string loadColorFromDatabase(std::shared_ptr<Database> database, std::string userName); 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, 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, const char *message, std::string userName, std::string color);
void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color); void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color);

View File

@@ -224,6 +224,10 @@ namespace Yc {
} }
bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){ 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 #ifdef YC_DEBUG
std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl; std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl;
std::cout << "[Debug] Available rooms: "; std::cout << "[Debug] Available rooms: ";
@@ -237,7 +241,7 @@ namespace Yc {
#ifdef YC_DEBUG #ifdef YC_DEBUG
std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl; std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl;
#endif #endif
if (room->name() == roomName && room->accessAllowed(userName, password)) { if (room->name() == roomName && room->accessAllowed(userName, password, user)) {
#ifdef YC_DEBUG #ifdef YC_DEBUG
std::cout << "[Debug] Room found and access allowed!" << std::endl; std::cout << "[Debug] Room found and access allowed!" << std::endl;
#endif #endif
@@ -251,7 +255,7 @@ namespace Yc {
} }
bool Server::changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password) { 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; return false;
} }

View File

@@ -22,6 +22,7 @@ namespace Yc {
std::vector<std::string> roomList(); std::vector<std::string> roomList();
Json::Value jsonRoomList(); 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);
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); bool changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password);
int _socket; int _socket;
std::shared_ptr<Yc::Lib::Config> _config; std::shared_ptr<Yc::Lib::Config> _config;

View File

@@ -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) { 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) { for (auto &room: _rooms) {
if (room->name() == roomName && room->accessAllowed(userName, password)) { if (room->name() == roomName && room->accessAllowed(userName, password, user)) {
return true; 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) { 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; return false;
} }

View File

@@ -44,6 +44,7 @@ public:
// Room management // Room management
std::vector<std::string> roomList(); 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);
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); bool changeRoom(std::shared_ptr<ChatUser> user, const std::string& newRoom, const std::string& password);
// User management // User management

View File

@@ -2,6 +2,14 @@
#include <random> #include <random>
#include <string> #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 Yc {
namespace Lib { namespace Lib {
@@ -29,5 +37,84 @@ namespace Yc {
return result; 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 Lib
} // namespace Yp } // namespace Yp

View File

@@ -11,6 +11,7 @@ namespace Yc {
public: public:
Tools(); Tools();
static std::string generateRandomString(size_t length = 16); static std::string generateRandomString(size_t length = 16);
static std::string decryptAES256ECB(const std::string& encryptedHex);
}; };
} // namespace Lib } // namespace Lib