diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 370b5d9fe9..e42203069a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -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; DomainGatekeeper::DomainGatekeeper(DomainServer* server) : @@ -112,7 +114,6 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetBytesLeftToRead() > 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. @@ -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 @@ -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); @@ -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"; @@ -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; @@ -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; @@ -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) { @@ -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 @@ -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(); @@ -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; @@ -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) { @@ -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); @@ -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. @@ -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; @@ -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 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."; } \ No newline at end of file diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index f2bcd6d2f5..a1b405ee6c 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -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, diff --git a/libraries/networking/src/LDAPAccount.cpp b/libraries/networking/src/LDAPAccount.cpp index 85fd7342d1..0fea7e6b14 100644 --- a/libraries/networking/src/LDAPAccount.cpp +++ b/libraries/networking/src/LDAPAccount.cpp @@ -16,62 +16,133 @@ #include #include #include +#include +#include +#include #include 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(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 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 roles; + + // Prepare the password as a berval. + berval creds{}; + creds.bv_val = const_cast(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(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) { diff --git a/libraries/networking/src/LDAPAccount.h b/libraries/networking/src/LDAPAccount.h index 3ea2980cee..48cee1563a 100644 --- a/libraries/networking/src/LDAPAccount.h +++ b/libraries/networking/src/LDAPAccount.h @@ -13,6 +13,7 @@ #ifndef LDAPACCOUNT_H #define LDAPACCOUNT_H +#include #include class LDAPAccount : public QObject { @@ -20,5 +21,8 @@ class LDAPAccount : public QObject { public: static bool isValidCredentials(const QString& username, const QString& password); static const char* toChar(const QString& str); + static std::vector getRolesAsStrings(const QString& username, const QString& password); +private: + static LDAP* initialize(); }; #endif //LDAPACCOUNT_H diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 48399c4b88..a6d7c33895 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -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; }}