Skip to content

Commit

Permalink
Added roles from LDAP user.
Browse files Browse the repository at this point in the history
Signed-off-by: armored-dragon <[email protected]>
  • Loading branch information
Armored-Dragon committed Oct 17, 2024
1 parent ff1903c commit 4ad0d73
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 58 deletions.
52 changes: 28 additions & 24 deletions domain-server/src/DomainGatekeeper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "WarningsSuppression.h"
#include "LDAPAccount.h"

// TODO: A lot of references to "lowerUsername". Usernames are no longer lower case and the extra variable should be removed.

using SharedAssignmentPointer = QSharedPointer<Assignment>;

DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
Expand Down Expand Up @@ -112,7 +114,6 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
if (message->getBytesLeftToRead() > 0) {
// Read domain username from packet.
packetStream >> domainUsername;
domainUsername = domainUsername.toLower(); // Domain usernames are case-insensitive; internally lower-case.

if (message->getBytesLeftToRead() > 0) {
// Read domain tokens from packet.
Expand Down Expand Up @@ -485,7 +486,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect

QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
if (!username.isEmpty()) {
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
const QUuid& connectionToken = _connectionTokenHash.value(username);

if (usernameSignature.isEmpty() || connectionToken.isNull()) {
// user is attempting to prove their identity to us, but we don't have enough information
Expand All @@ -503,7 +504,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
// they sent us a username and the signature verifies it
getGroupMemberships(username);
verifiedUsername = username.toLower();
verifiedUsername = username;
} else {
// they sent us a username, but it didn't check out
requestUserPublicKey(username);
Expand Down Expand Up @@ -561,7 +562,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
bool isValidLDAPCredentials = LDAPAccount::isValidCredentials(domainUsername, domainAccessToken);
if (isValidLDAPCredentials) {
verifiedDomainUsername = domainUsername;
requestDomainLDAPUserFinished(verifiedDomainUsername);
requestDomainLDAPUserFinished(verifiedDomainUsername, domainAccessToken);
}
else {
qDebug() << "LDAP Sign in failed";
Expand Down Expand Up @@ -715,7 +716,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
const QByteArray& usernameSignature,
const SockAddr& senderSockAddr) {
// it's possible this user can be allowed to connect, but we need to check their username signature
auto lowerUsername = username.toLower();
auto lowerUsername = username;
KeyFlagPair publicKeyPair = _userPublicKeys.value(lowerUsername);

QByteArray publicKeyArray = publicKeyPair.first;
Expand Down Expand Up @@ -837,7 +838,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username, bool isOpti
return;
}

QString lowerUsername = username.toLower();
QString lowerUsername = username;
if (_inFlightPublicKeyRequests.contains(lowerUsername)) {
// public-key request for this username is already flight, not rerequesting
return;
Expand Down Expand Up @@ -868,7 +869,7 @@ QString extractUsernameFromPublicKeyRequest(QNetworkReply* requestReply) {
if (usernameRegex.indexIn(requestReply->url().toString()) != -1) {
username = usernameRegex.cap(1);
}
return username.toLower();
return username;
}

void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply* requestReply) {
Expand All @@ -882,9 +883,9 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply* requestReply) {
const QString JSON_DATA_KEY = "data";
const QString JSON_PUBLIC_KEY_KEY = "public_key";

qDebug().nospace() << "Extracted " << (isOptimisticKey ? "optimistic " : " ") << "public key for " << username.toLower();
qDebug().nospace() << "Extracted " << (isOptimisticKey ? "optimistic " : " ") << "public key for " << username;

_userPublicKeys[username.toLower()] =
_userPublicKeys[username] =
{
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()),
isOptimisticKey
Expand Down Expand Up @@ -939,7 +940,7 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const S

void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const SockAddr& senderSockAddr) {
// get the existing connection token or create a new one
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
QUuid& connectionToken = _connectionTokenHash[username];

if (connectionToken.isNull()) {
connectionToken = QUuid::createUuid();
Expand Down Expand Up @@ -1065,7 +1066,7 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) {
json["groups"] = groupIDs;

// if we've already asked, wait for the answer before asking again
QString lowerUsername = username.toLower();
QString lowerUsername = username;
if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) {
// public-key request for this username is already flight, not rerequesting
return;
Expand Down Expand Up @@ -1093,7 +1094,7 @@ QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) {
if (usernameRegex.indexIn(requestReply->url().toString()) != -1) {
username = usernameRegex.cap(1);
}
return username.toLower();
return username;
}

void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply* requestReply) {
Expand Down Expand Up @@ -1175,7 +1176,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply* requ
_domainOwnerFriends.clear();
QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray();
for (int i = 0; i < friends.size(); i++) {
_domainOwnerFriends += friends.at(i).toString().toLower();
_domainOwnerFriends += friends.at(i).toString();
}
} else {
qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
Expand Down Expand Up @@ -1311,8 +1312,8 @@ void DomainGatekeeper::requestDomainUserFinished() {

if (200 <= httpStatus && httpStatus < 300) {

QString username = rootObject.value("username").toString().toLower();
QString email = rootObject.value("email").toString().toLower();
QString username = rootObject.value("username").toString();
QString email = rootObject.value("email").toString();

if (_inFlightDomainUserIdentityRequests.contains(username) || _inFlightDomainUserIdentityRequests.contains(email)) {
// Success! Verified user.
Expand All @@ -1327,7 +1328,7 @@ void DomainGatekeeper::requestDomainUserFinished() {
auto userRoles = rootObject.value("roles").toArray();
foreach (auto role, userRoles) {
// Distinguish domain groups from directory services groups by adding a leading special character.
domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower());
domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString());
}
_domainGroupMemberships[username] = domainUserGroups;

Expand All @@ -1344,22 +1345,25 @@ void DomainGatekeeper::requestDomainUserFinished() {
_inFlightDomainUserIdentityRequests.clear();
}
}
void DomainGatekeeper::requestDomainLDAPUserFinished(const QString& username) {
void DomainGatekeeper::requestDomainLDAPUserFinished(const QString& username, const QString& password) {
// Adds the user as a verified user?
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));

// No longer waiting for the identity request?
_inFlightDomainUserIdentityRequests.remove(username);

// TODO FIXME: Roles
// User user's LDAP roles as domain groups.
// Add "ldap" as a generic role for ldap authenticated user.
QStringList domainUserGroups;
domainUserGroups.append("ldap");
// auto userRoles = rootObject.value("roles").toArray();
// foreach (auto role, userRoles) {
// // Distinguish domain groups from directory services groups by adding a leading special character.
// domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower());
// }

std::vector<std::string> roles = LDAPAccount::getRolesAsStrings(username, password);

// For each of the roles returned by the rolesAsStrings function, add it as a role for the ldap server.
foreach (auto role, roles) {
QString qStr = QString(role.c_str());
domainUserGroups.append(qStr);
}

_domainGroupMemberships[username] = domainUserGroups;
qDebug() << "LDAP user '" << username << "' finished.";
}
2 changes: 1 addition & 1 deletion domain-server/src/DomainGatekeeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private slots:

// Login and groups for domain, separate from directory services.
void requestDomainUserFinished();
void requestDomainLDAPUserFinished(const QString& username);
void requestDomainLDAPUserFinished(const QString& username, const QString& password);

private:
SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
Expand Down
135 changes: 103 additions & 32 deletions libraries/networking/src/LDAPAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,62 +16,133 @@
#include <iostream>
#include <cstring>
#include <cctype>
#include <vector>
#include <regex>
#include <string>

#include <ldap.h>

bool LDAPAccount::isValidCredentials(const QString& username, const QString& password) {
int result;
LDAP* ldapHandle = LDAPAccount::initialize(); // Initialize the ldap connection.

// Prepare the password as a berval.
berval creds{};
creds.bv_val = const_cast<char*>(toChar(password));
creds.bv_len = strlen(toChar(password));

// Perform SASL bind
result = ldap_sasl_bind_s(ldapHandle, toChar(username), LDAP_SASL_SIMPLE, &creds, nullptr, nullptr, nullptr);
if (result != LDAP_SUCCESS) {
// Login info is invalid
qDebug(networking) << "Failed trying to bind to LDAP. Status: " << result << ". " << ldap_err2string(result);
ldap_unbind_ext(ldapHandle, nullptr, nullptr);
return false;
}

qDebug(networking) << "Successfully signed in '" << username << "' into the LDAP server";

// Unbind and free the resources
ldap_unbind_ext(ldapHandle, nullptr, nullptr);

return true;
}

std::vector<std::string> LDAPAccount::getRolesAsStrings(const QString& username, const QString& password) {
berval** memberOf;
std::regex cnRegex(R"(cn=([^,]+))");
std::smatch matches;
LDAPMessage *result, *entry;
LDAP* ldapHandle = LDAPAccount::initialize(); // Initialize the ldap connection.

std::vector<std::string> roles;

// Prepare the password as a berval.
berval creds{};
creds.bv_val = const_cast<char*>(toChar(password));
creds.bv_len = strlen(toChar(password));

// Perform SASL bind
int saslBind = ldap_sasl_bind_s(ldapHandle, toChar(username), LDAP_SASL_SIMPLE, &creds, nullptr, nullptr, nullptr);

const char* usernameChar = toChar(username); // Change the QString to char*
int rc;

char *attrs[] = { "memberOf", nullptr };

rc = ldap_search_ext_s(
ldapHandle,
usernameChar,
LDAP_SCOPE_SUBTREE,
nullptr,
attrs,
0,
nullptr,
nullptr,
nullptr,
0,
&result
);

if (rc != LDAP_SUCCESS) {
// Failed preforming the LDAP search
qDebug(networking) << "LDAP search failed: "<< ldap_err2string(rc);
return {};
}

entry = ldap_first_entry(ldapHandle, result);
if (entry == nullptr) {
// Failed to find the user
qDebug(networking) << "LDAP search returned no users.";
return {};
}

memberOf = ldap_get_values_len(ldapHandle, entry, "memberOf");
if (memberOf == nullptr) {
// User does not have any roles to return
qDebug(networking) << "User does not have any roles.";
return {};
}

for (int i = 0; memberOf[i] != nullptr; i++) {
const std::string role = memberOf[i]->bv_val;

if (std::regex_search(role, matches, cnRegex)){
std::string cn = matches[0]; // Extract the CN value
cn.erase(0, 3); // Remove the first three characters (cn=)
roles.emplace_back(cn); // Append to the list
}
}

return roles;
}

LDAP* LDAPAccount::initialize(){
// TODO FIXME: Check to see if username/password is null
// TODO FIXME: Get domain URL or LDAP url?

// Change the QString to char*
const char* usernameChar = toChar(username);
const char* passwordChar = toChar(password);

LDAP* ldapHandle;
const char* ldapURI = "ldap://localhost:3389";
const char* bindDN = usernameChar;
const char* ldapPassword = passwordChar;
// const char* saslMechanism = "LDAP_SASL_SIMPLE";
ulong version = LDAP_VERSION3;

// Prepare the password as a berval.
berval creds{};
creds.bv_val = const_cast<char*>(ldapPassword); // Cast to char*
creds.bv_len = strlen(ldapPassword); // Set length of password

// Initialize the LDAP library
int result = ldap_initialize(&ldapHandle, ldapURI);
if (result != LDAP_SUCCESS) {
// LDAP coult not be initialized for some reason.
qDebug(networking) << "LDAP could not be initialized";
return false;
return 0;
}

// Set the LDAP version to 3
result = ldap_set_option(ldapHandle, LDAP_OPT_PROTOCOL_VERSION, &version);
if (result != LDAP_SUCCESS) {
// Could not update the LDAP version for some reason.
qDebug(networking) << "Could not update LDAP version";
return false;
return 0;
}

// if (connectSuccess == LDAP_SUCCESS) {
// qDebug(networking) << "Successfully connected to LDAP";
// }

// Perform SASL bind
result = ldap_sasl_bind_s(ldapHandle, bindDN, LDAP_SASL_SIMPLE, &creds, nullptr, nullptr, nullptr);
if (result != LDAP_SUCCESS) {
// Login info is invalid
qDebug(networking) << "Failed trying to bind to LDAP. Status: " << result << ". " << ldap_err2string(result);
ldap_unbind_ext(ldapHandle, nullptr, nullptr);
return false;
}

qDebug(networking) << "Successfully signed in '" << username << "' into the LDAP server";

// Unbind and free the resources
ldap_unbind_ext(ldapHandle, nullptr, nullptr);

return true;
return ldapHandle;
}

const char* LDAPAccount::toChar(const QString& str) {
Expand Down
4 changes: 4 additions & 0 deletions libraries/networking/src/LDAPAccount.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
#ifndef LDAPACCOUNT_H
#define LDAPACCOUNT_H

#include <ldap.h>
#include <QtCore/QObject>

class LDAPAccount : public QObject {
Q_OBJECT
public:
static bool isValidCredentials(const QString& username, const QString& password);
static const char* toChar(const QString& str);
static std::vector<std::string> getRolesAsStrings(const QString& username, const QString& password);
private:
static LDAP* initialize();
};
#endif //LDAPACCOUNT_H
2 changes: 1 addition & 1 deletion libraries/networking/src/NodePermissions.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class NodePermissions {
void setVerifiedUserName(QString userName) { _verifiedUserName = userName; }
const QString& getVerifiedUserName() const { return _verifiedUserName; }

void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); }
void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName; }
const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; }

void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }}
Expand Down

0 comments on commit 4ad0d73

Please sign in to comment.