Commit 19049e94 authored by jan.koester's avatar jan.koester
Browse files

authdb support editor

parent 776f8a29
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -345,10 +345,10 @@
            nameSpan.textContent = '#' + rev.revision + '' + rev.name;
            info.appendChild(nameSpan);

            var dateSpan = document.createElement('div');
            dateSpan.className = 'doc-item-date';
            dateSpan.textContent = rev.created || '';
            info.appendChild(dateSpan);
            var metaSpan = document.createElement('div');
            metaSpan.className = 'doc-item-date';
            metaSpan.textContent = (rev.author || '') + (rev.author && rev.created ? ' · ' : '') + (rev.created || '');
            info.appendChild(metaSpan);

            item.appendChild(info);

+6 −2
Original line number Diff line number Diff line
@@ -765,11 +765,15 @@ void webedit::Api::handleSaveDocument(libhttppp::HttpRequest &curreq,
    std::string xml = exportTreeXml(doc);
    std::string docId;

    // Get author from session
    std::string author;
    _session.getData(sessionid, "username", author);

    if (doc.currentDocId.empty()) {
        docId = _db.saveDocument(doc.currentDocName, xml);
        docId = _db.saveDocument(doc.currentDocName, xml, author);
        doc.currentDocId = docId;
    } else {
        docId = _db.updateDocument(doc.currentDocId, doc.currentDocName, xml);
        docId = _db.updateDocument(doc.currentDocId, doc.currentDocName, xml, author);
    }

    json_object *resp = json_object_new_object();
+15 −7
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ void webedit::Database::initTables() {
        <<   "revision integer NOT NULL,"
        <<   "name varchar(255) NOT NULL,"
        <<   "xml text NOT NULL,"
        <<   "author varchar(255) DEFAULT '',"
        <<   "created timestamp DEFAULT CURRENT_TIMESTAMP,"
        <<   "PRIMARY KEY (id),"
        <<   "FOREIGN KEY (document_id) REFERENCES documents (id)"
@@ -94,7 +95,8 @@ void webedit::Database::initTables() {
    _db->exec(sql, res);
}

std::string webedit::Database::saveDocument(const std::string &name, const std::string &xml) {
std::string webedit::Database::saveDocument(const std::string &name, const std::string &xml,
                                            const std::string &author) {
    uuid::uuid docId;
    docId.generate();

@@ -116,12 +118,14 @@ std::string webedit::Database::saveDocument(const std::string &name, const std::
    res.clear();
    escapedName.clear();
    escapedXml.clear();
    std::vector<char> escapedAuthor;

    sql << "INSERT INTO document_revisions (id, document_id, revision, name, xml) VALUES ('"
    sql << "INSERT INTO document_revisions (id, document_id, revision, name, xml, author) VALUES ('"
        << revId.c_str() << "','"
        << docId.c_str() << "',1,'"
        << dbpp::SQL::escaped(escapedName, name.c_str()) << "','"
        << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "');";
        << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','"
        << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');";

    _db->exec(sql, res);

@@ -130,7 +134,8 @@ std::string webedit::Database::saveDocument(const std::string &name, const std::

std::string webedit::Database::updateDocument(const std::string &id,
                                               const std::string &name,
                                               const std::string &xml) {
                                               const std::string &xml,
                                               const std::string &author) {
    dbpp::SQL sql;
    dbpp::DBResult res;
    std::vector<char> escapedName, escapedXml, escapedId;
@@ -165,13 +170,15 @@ std::string webedit::Database::updateDocument(const std::string &id,
    escapedName.clear();
    escapedXml.clear();
    escapedId.clear();
    std::vector<char> escapedAuthor;

    sql << "INSERT INTO document_revisions (id, document_id, revision, name, xml) VALUES ('"
    sql << "INSERT INTO document_revisions (id, document_id, revision, name, xml, author) VALUES ('"
        << revId.c_str() << "','"
        << dbpp::SQL::escaped(escapedId, id.c_str()) << "',"
        << nextRev << ",'"
        << dbpp::SQL::escaped(escapedName, name.c_str()) << "','"
        << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "');";
        << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','"
        << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');";

    _db->exec(sql, res);

@@ -246,7 +253,7 @@ std::string webedit::Database::listRevisions(const std::string &docId) {
    dbpp::DBResult res;
    std::vector<char> escapedId;

    sql << "SELECT id, revision, name, created FROM document_revisions WHERE document_id='"
    sql << "SELECT id, revision, name, created, author FROM document_revisions WHERE document_id='"
        << dbpp::SQL::escaped(escapedId, docId.c_str())
        << "' ORDER BY revision DESC;";

@@ -260,6 +267,7 @@ std::string webedit::Database::listRevisions(const std::string &docId) {
        json_object_object_add(obj, "revision", json_object_new_int(std::stoi(std::string(res[i][1]))));
        json_object_object_add(obj, "name", json_object_new_string(res[i][2]));
        json_object_object_add(obj, "created", json_object_new_string(res[i][3]));
        json_object_object_add(obj, "author", json_object_new_string(res[i][4]));
        json_object_array_add(arr, obj);
    }

+3 −2
Original line number Diff line number Diff line
@@ -41,9 +41,10 @@ namespace webedit {
        void initTables();

        // Document CRUD
        std::string saveDocument(const std::string &name, const std::string &xml);
        std::string saveDocument(const std::string &name, const std::string &xml,
                                 const std::string &author = "");
        std::string updateDocument(const std::string &id, const std::string &name,
                                   const std::string &xml);
                                   const std::string &xml, const std::string &author = "");
        bool loadDocument(const std::string &id, std::string &name, std::string &xml);
        bool deleteDocument(const std::string &id);
        std::string listDocuments(); // Returns JSON array
+89 −45
Original line number Diff line number Diff line
@@ -48,6 +48,16 @@ webedit::Server::Server(std::vector<netplus::socket*> serversocket,
      _session(db.getDb()),
      _api(db, _session, plugins),
      _plugins(plugins) {

    // Create connection pools for each auth source
    for (size_t i = 0; i < _config.getAuthSourceCount(); ++i) {
        const auto &src = _config.getAuthSource(i);
        _authPools.push_back(std::make_unique<authdb::client::ConnectionPool>(
            src.url, src.clientName, src.clientSecret, 4));
    }

    // Auto-create login GPO on all auth sources
    initGPO();
}

webedit::Server::~Server() {
@@ -219,26 +229,30 @@ void webedit::Server::sendJsonResponse(libhttppp::HttpRequest &curreq,
}

bool webedit::Server::isAuthenticated(const std::string &authid) {
    if (authid.empty() || _config.getAuthSourceCount() == 0)
    if (authid.empty() || _authPools.empty())
        return false;

    uuid::uuid sid(authid.c_str());

    for (size_t i = 0; i < _config.getAuthSourceCount(); ++i) {
        const auto &src = _config.getAuthSource(i);
    for (size_t i = 0; i < _authPools.size(); ++i) {
        try {
            authdb::client::ClientConnection authcon;
            authcon.setUrl(src.url);
            authcon.setClientName(src.clientName);
            authcon.setClientSecret(src.clientSecret);
            authcon.setSessionID(sid);
            auto pooled = _authPools[i]->acquire();
            pooled.setSessionID(sid);

            authdb::client::Client clt(authcon);

            if (clt.ClientAuth() &&
                clt.GPOcheck(uuid::uuid("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6"))) {
            try {
                if (pooled.client().ClientAuth() &&
                    pooled.client().GPOcheck(uuid::uuid("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6"))) {
                    return true;
                }
            } catch (...) {
                // Reconnect and retry once
                pooled.connection().reConnect();
                pooled.setSessionID(sid);
                if (pooled.client().ClientAuth() &&
                    pooled.client().GPOcheck(uuid::uuid("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6"))) {
                    return true;
                }
            }
        } catch (...) {
            continue;
        }
@@ -248,7 +262,7 @@ bool webedit::Server::isAuthenticated(const std::string &authid) {

void webedit::Server::handleLogin(libhttppp::HttpRequest &curreq,
                                   const std::string &sessionid) {
    if (_config.getAuthSourceCount() == 0) {
    if (_authPools.empty()) {
        sendJsonResponse(curreq, HTTP500,
            "{\"error\":\"No auth source configured\"}");
        return;
@@ -293,45 +307,56 @@ void webedit::Server::handleLogin(libhttppp::HttpRequest &curreq,
        return;
    }

    try {
        authdb::client::ClientConnection authcon;
        authcon.setUrl(src->url);
        authcon.setClientName(src->clientName);
        authcon.setClientSecret(src->clientSecret);
    // Find the pool index for this auth source
    size_t poolIdx = 0;
    for (size_t i = 0; i < _config.getAuthSourceCount(); ++i) {
        if (&_config.getAuthSource(i) == src) {
            poolIdx = i;
            break;
        }
    }

        authdb::client::Client clt(authcon);
    try {
        auto pooled = _authPools[poolIdx]->acquire();

        if (!clt.ClientAuth()) {
        if (!pooled.client().ClientAuth()) {
            sendJsonResponse(curreq, HTTP500,
                "{\"error\":\"Client authentication failed\"}");
            return;
        }

        authcon.setUsername(user);
        authcon.setUserPassword(password);
        pooled.connection().setUsername(user);
        pooled.connection().setUserPassword(password);

        if (!clt.UserAuth()) {
        if (!pooled.client().UserAuth()) {
            sendJsonResponse(curreq, HTTP401,
                "{\"error\":\"Invalid credentials\"}");
            return;
        }

        // Check GPO "Allow Login"
        if (!clt.GPOcheck(uuid::uuid("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6"))) {
            clt.remSession();
        if (!pooled.client().GPOcheck(uuid::uuid("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6"))) {
            pooled.client().remSession();
            sendJsonResponse(curreq, HTTP403,
                "{\"error\":\"Login not permitted\"}");
            return;
        }

        // Store authid in session
        std::string authid = authcon.getSessionID().c_str();
        std::string authid = pooled.connection().getSessionID().c_str();
        _session.addData(sessionid, "authid", authid);

        // Get user info
        // Get user info and store in session
        authdb::SessionData sdat;
        clt.SessionInfo(sdat);
        pooled.client().SessionInfo(sdat);

        std::string displayname = sdat.getUsername();
        uuid::uuid uid;
        sdat.getUid(uid);
        std::string uidStr = uid.c_str();

        _session.addData(sessionid, "uid", uidStr);
        _session.addData(sessionid, "username", displayname);

        // Set authid cookie and return success
        libhttppp::HttpResponse resp;
@@ -345,17 +370,18 @@ void webedit::Server::handleLogin(libhttppp::HttpRequest &curreq,
        json_object *result = json_object_new_object();
        json_object_object_add(result, "status", json_object_new_string("ok"));
        json_object_object_add(result, "username", json_object_new_string(displayname.c_str()));
        json_object_object_add(result, "uid", json_object_new_string(uidStr.c_str()));

        const char *jsonStr = json_object_to_json_string_ext(result, JSON_C_TO_STRING_PLAIN);
        resp.send(curreq, jsonStr, strlen(jsonStr));
        json_object_put(result);

    } catch (authdb::AuthBackendError &e) {
        sendJsonResponse(curreq, HTTP500,
            std::string("{\"error\":\"Auth error: ") + e.what() + "\"}");
    } catch (std::exception &e) {
        sendJsonResponse(curreq, HTTP401,
            std::string("{\"error\":\"") + e.what() + "\"}");
        json_object *errResp = json_object_new_object();
        json_object_object_add(errResp, "error", json_object_new_string(e.what()));
        const char *errStr = json_object_to_json_string_ext(errResp, JSON_C_TO_STRING_PLAIN);
        sendJsonResponse(curreq, HTTP401, errStr);
        json_object_put(errResp);
    }
}

@@ -366,24 +392,20 @@ void webedit::Server::handleLogout(libhttppp::HttpRequest &curreq,
    if (!authid.empty()) {
        // Remove authdb session
        uuid::uuid sid(authid.c_str());
        for (size_t i = 0; i < _config.getAuthSourceCount(); ++i) {
            const auto &src = _config.getAuthSource(i);
        for (size_t i = 0; i < _authPools.size(); ++i) {
            try {
                authdb::client::ClientConnection authcon;
                authcon.setUrl(src.url);
                authcon.setClientName(src.clientName);
                authcon.setClientSecret(src.clientSecret);
                authcon.setSessionID(sid);

                authdb::client::Client clt(authcon);
                if (clt.ClientAuth()) {
                    clt.remSession();
                auto pooled = _authPools[i]->acquire();
                pooled.setSessionID(sid);
                if (pooled.client().ClientAuth()) {
                    pooled.client().remSession();
                    break;
                }
            } catch (...) {}
        }

        _session.delData(sessionid, "authid");
        _session.delData(sessionid, "uid");
        _session.delData(sessionid, "username");
    }

    // Clear the authid cookie
@@ -431,6 +453,28 @@ void webedit::Server::handleI18n(libhttppp::HttpRequest &curreq) {
    json_object_put(obj);
}

void webedit::Server::initGPO() {
    const uuid::uuid loginGpoId("e7d3b8b3-4825-11f0-ae2b-3cecefce9cb6");
    for (size_t i = 0; i < _authPools.size(); ++i) {
        try {
            auto pooled = _authPools[i]->acquire();
            if (!pooled.client().ClientAuth())
                continue;

            if (!pooled.client().GPOexits(loginGpoId)) {
                std::vector<std::string> members;
                pooled.client().GPOadd(loginGpoId, "Blogi: Allow Login",
                                       "Allow Login to webinterface", true, members);
                std::cout << "Created GPO 'Blogi: Allow Login' on auth source: "
                          << _config.getAuthSource(i).domain << std::endl;
            }
        } catch (std::exception &e) {
            std::cerr << "Warning: could not ensure login GPO on auth source '"
                      << _config.getAuthSource(i).domain << "': " << e.what() << std::endl;
        }
    }
}

std::string webedit::Server::getMimeType(const std::string &path) {
    if (path.ends_with(".html") || path.ends_with(".htm"))
        return "text/html; charset=utf-8";
Loading