Compare commits

...

6 Commits

21 changed files with 205 additions and 57 deletions

30
cache/nocache.h vendored Normal file
View File

@ -0,0 +1,30 @@
#include "icache.h"
class NoCache : public ICache
{
public:
NoCache(std::string p)
{
}
virtual std::optional<std::string> get(std::string_view key) const
{
return {};
}
virtual void put(std::string_view key, std::string val)
{
return;
}
virtual void remove(std::string_view key)
{
return;
}
virtual void removePrefix(std::string_view prefix)
{
return;
}
virtual void clear()
{
return;
}
};

View File

@ -13,7 +13,7 @@
#include "permissionsdao.h"
class Database
{
private:
protected:
std::string connnectionstring;
public:

View File

@ -54,7 +54,7 @@ void SessionDaoSqlite::fillSession(int userid, Session &sess)
{
if(userid > -1)
{
UserDaoSqlite userDao{this->db};
UserDaoSqlite userDao{*this->db};
auto u = userDao.find(userid);
if(u)
{

View File

@ -18,23 +18,43 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <atomic>
#include "sqlite.h"
#include "../logger.h"
#include "pagedaosqlite.h"
#include "revisiondaosqlite.h"
#include "sessiondaosqlite.h"
#include "sqlite_modern_cpp.h"
#include "userdaosqlite.h"
#include "categorydaosqlite.h"
#include "exceptions.h"
#include "permissionsdaosqlite.h"
thread_local sqlite::database *Sqlite::db = nullptr;
std::atomic<int> instances = 0;
Sqlite::Sqlite(std::string path) : Database(path)
{
this->db = std::make_shared<sqlite::database>(path);
*db << "PRAGMA journal_mode=WAL;";
instances++;
if(instances.load() > 1)
{
std::cerr << "temporal (yeah, right) HACK... only one instance allowed" << std::endl;
abort();
}
}
std::mutex dbmutex;
sqlite::database &Sqlite::database() const
{
if(Sqlite::db == nullptr)
{
sqlite::sqlite_config config;
config.flags = config.flags | sqlite::OpenFlags::FULLMUTEX;
std::lock_guard<std::mutex> dbguard(dbmutex);
Sqlite::db = new sqlite::database(this->connnectionstring, config);
*Sqlite::db << "PRAGMA journal_mode=WAL;";
*Sqlite::db << "PRAGMA busy_timeout=10000;";
}
return *Sqlite::db;
}
std::unique_ptr<RevisionDao> Sqlite::createRevisionDao() const
{
return create<RevisionDaoSqlite>();
@ -67,27 +87,20 @@ std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
void Sqlite::beginTransaction()
{
if(!inTransaction)
{
*db << "begin;";
inTransaction = true;
}
*db << "begin;";
}
void Sqlite::rollbackTransaction()
{
if(inTransaction)
{
*db << "rollback;";
inTransaction = false;
}
*db << "rollback;";
}
void Sqlite::commitTransaction()
{
if(inTransaction)
{
*db << "commit;";
inTransaction = false;
}
*db << "commit;";
}
Sqlite::~Sqlite()
{
delete this->db;
}

View File

@ -8,14 +8,15 @@
class Sqlite : public Database
{
private:
bool inTransaction = false;
std::shared_ptr<sqlite::database> db;
static thread_local sqlite::database *db;
template <class T> std::unique_ptr<T> create() const
{
return std::make_unique<T>(db);
return std::make_unique<T>(database());
}
sqlite::database &database() const;
public:
Sqlite(std::string path);
std::unique_ptr<PageDao> createPageDao() const;
@ -27,6 +28,7 @@ class Sqlite : public Database
void beginTransaction();
void commitTransaction();
void rollbackTransaction();
virtual ~Sqlite();
};
#endif // SQLITE_H

View File

@ -12,20 +12,20 @@
class SqliteDao
{
protected:
std::shared_ptr<sqlite::database> db;
sqlite::database *db = nullptr;
public:
SqliteDao()
{
}
SqliteDao(std::shared_ptr<sqlite::database> db)
SqliteDao(sqlite::database &db)
{
this->db = db;
this->db = &db;
}
void setDb(std::shared_ptr<sqlite::database> db)
void setDb(sqlite::database &db)
{
this->db = db;
this->db = &db;
}
inline void throwFrom(const sqlite::sqlite_exception &e) const

View File

@ -1,8 +1,9 @@
#include "dynamiccontent.h"
DynamicContent::DynamicContent(Template &templ, Database &database, UrlProvider &provider)
DynamicContent::DynamicContent(Template &templ, Database &database, UrlProvider &provider, Session &session)
{
this->templ = &templ;
this->database = &database;
this->urlProvider = &provider;
this->userSession = &session;
}

View File

@ -10,11 +10,12 @@ class DynamicContent
Template *templ;
Database *database;
UrlProvider *urlProvider;
Session *userSession;
std::string argument;
public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider);
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider, Session &session);
virtual std::string render() = 0;
virtual void setArgument(std::string argument)
{

View File

@ -9,18 +9,20 @@ private:
Template *templ;
Database *db;
UrlProvider *urlProvider;
Session *session;
public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider)
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider, Session &session)
{
this->templ = &templ;
this->db = &db;
this->urlProvider = &urlProvider;
this->session = &session;
}
template <class T> inline std::shared_ptr<T> createDynamicContent()
{
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider);
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider, *this->session);
}

View File

@ -6,14 +6,21 @@ std::string DynamicContentPostList::render()
auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao();
auto permissionDao = this->database->createPermissionsDao();
QueryOption option;
option.includeInvisible = false;
auto members = categoryDao->fetchMembers(this->argument, option);
std::vector<std::pair<std::string, time_t>> pageList;
for(const Page &member : members)
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, revision->timestamp});
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead()) /* TODO: Maybe add canList() */
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, revision->timestamp});
}
}
std::sort(pageList.begin(), pageList.end(),
[](std::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });

View File

@ -98,7 +98,10 @@ Response Handler::handle(const Request &r)
Permissions Handler::effectivePermissions(std::string page)
{
return this->database->createPermissionsDao()
->find(page, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
Permissions &userPerms = this->userSession->user.permissions;
if(userPerms.isAdmin())
{
return userPerms;
}
return this->database->createPermissionsDao()->find(page, this->userSession->user.login).value_or(userPerms);
}

View File

@ -1,19 +1,19 @@
#include "handlerfeedgenerator.h"
#include "../parser.h"
#include "../revisionrenderer.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories)
{
auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao();
auto permissionDao = this->database->createPermissionsDao();
std::vector<EntryRevisionPair> result;
QueryOption option;
option.includeInvisible = false;
// option.limit = 20;
auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; };
std::set<Page, decltype(comppage)> members (comppage);
std::set<Page, decltype(comppage)> members(comppage);
if(categories.empty())
{
auto pages = pageDao->getPageList(option);
@ -34,8 +34,13 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch
}
for(const Page &member : members)
{
auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
result.push_back({member, revision});
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead())
{
auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
result.push_back({member, revision});
}
}
std::sort(result.begin(), result.end(),
[](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; });
@ -68,7 +73,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
subtitle = "All pages";
}
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
for(const EntryRevisionPair &entry : entries)
{

View File

@ -24,6 +24,7 @@ SOFTWARE.
#include "../parser.h"
#include "../revisionrenderer.h"
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{
return effectivePermissions(page).canEdit();
@ -56,7 +57,8 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{
if(!effectivePermissions(from).canRead())
{
return this->errorResponse("Permission denied", "No access permissions, so you can't use this page as a template");
return this->errorResponse("Permission denied",
"No access permissions, so you can't use this page as a template");
}
body = revisiondao->getCurrentForPage(from)->content;
}
@ -77,6 +79,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent);
std::vector<std::string> perms = parser.extractCommands("permissions", newContent);
Page page;
std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage)
@ -91,6 +94,33 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
}
pagename = rename;
}
for(const std::string &perm : perms)
{
auto splitted = utils::split(perm, '|');
if(splitted.size() != 2)
{
return this->errorResponse("Invalid command", "permissions command is misformated");
}
auto permissionDao = this->database->createPermissionsDao();
auto currentPermission = permissionDao->find(pagename, splitted[0]);
Permissions newPermissions = Permissions{splitted[1]};
if(!currentPermission || newPermissions != currentPermission.value())
{
if(this->userSession->user.permissions.canSetPagePerms())
{
permissionDao->save(pagename, splitted[0], newPermissions);
}
else
{
this->database->rollbackTransaction();
return errorResponse("Invalid permissions",
"You don't have permission to change page permissions");
}
}
}
page.current_revision = current_revision;
page.listed = !(visiblecmd == "0");
page.name = pagename;
@ -115,6 +145,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
}
catch(const DatabaseException &e)
{
this->database->rollbackTransaction();
Logger::debug() << "Error saving revision: " << e.what();
return errorResponse("Database error", "A database error occured while trying to save this revision");
}
@ -129,7 +160,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename));
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent);

View File

@ -138,8 +138,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
Response result;
result.setStatus(200);
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
std::string customtitle = parser.extractCommand("pagetitle", revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);

View File

@ -16,6 +16,8 @@ class IParser
public:
virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0;
virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const
{

View File

@ -82,6 +82,28 @@ std::string Parser::extractCommand(std::string cmdname, const std::string &conte
}
return "";
}
std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const
{
std::vector<std::string> result;
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
std::string_view view = content;
size_t pos = 0;
while((pos = view.find(cmd)) != std::string::npos)
{
view.remove_prefix(pos);
view.remove_prefix(cmd.size());
if((pos = view.find(cmdend)) != std::string::npos)
{
result.emplace_back(view.substr(0, pos));
}
}
return result;
}
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{
std::string linktag = match.str(1);
@ -148,7 +170,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings
std::regex tagfinder(
R"(\[(b|i|u|s|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])");
R"(\[(b|i|u|s|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])");
result = utils::regex_callback_replacer(
tagfinder, content,
[&](std::smatch &match)
@ -180,7 +202,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
}
if(tag == "code" || tag == "blockquote")
{
return "<pre><" + tag + ">"+ utils::strreplace(content, "\r\n", "\n") + "</"+tag+"></pre>";
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
}
return callback(tag, content);
});

View File

@ -9,7 +9,8 @@ class Parser : public IParser
std::string processImage(std::smatch &match) const;
public:
std::string extractCommand(std::string cmdname, const std::string &content) const;
std::string extractCommand(std::string cmdname, const std::string &content) const override;
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override;
std::vector<Headline> extractHeadlines(const std::string &content) const override;
std::vector<std::string> extractCategories(const std::string &content) const override;
using IParser::parse;

View File

@ -20,7 +20,8 @@ SOFTWARE.
*/
#include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING},
{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
@ -29,7 +30,8 @@ static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}};
{"can_search", PERM_CAN_SEARCH},
{"can_set_page_perms", PERM_CAN_SET_PAGE_PERMS}};
Permissions::Permissions(int permissions)
{

View File

@ -1,6 +1,7 @@
#ifndef PERMISSIONS_H
#define PERMISSIONS_H
#define PERM_CAN_NOTHING 0
#define PERM_CAN_READ 1 << 0
#define PERM_CAN_EDIT 1 << 1
#define PERM_CAN_PAGE_HISTORY 1 << 2
@ -11,6 +12,8 @@
#define PERM_CAN_SEE_CATEGORY_LIST 1 << 7
#define PERM_CAN_SEE_LINKS_HERE 1 << 8
#define PERM_CAN_SEARCH 1 << 9
#define PERM_CAN_SET_PAGE_PERMS 1 << 10
#define PERM_IS_ADMIN (1L<<31)-1
#include <string>
#include <map>
@ -54,10 +57,16 @@ class Permissions
return this->permissions;
}
bool canNothing() const
{
return this->permissions == PERM_CAN_NOTHING;
}
bool canRead() const
{
return this->permissions & PERM_CAN_READ;
}
bool canEdit() const
{
return this->permissions & PERM_CAN_EDIT;
@ -95,12 +104,27 @@ class Permissions
return this->permissions & PERM_CAN_SEE_PAGE_LIST;
}
bool canSetPagePerms() const
{
return this->permissions & PERM_CAN_SET_PAGE_PERMS;
}
bool isAdmin() const
{
return this->permissions == PERM_IS_ADMIN;
}
std::string toString() const
{
return Permissions::toString(this->permissions);
}
static std::string toString(int perms);
bool operator==(const Permissions &o) const
{
return this->permissions == o.permissions;
}
};
#endif // PERMISSIONS_H

View File

@ -37,6 +37,7 @@ SOFTWARE.
#include "urlprovider.h"
#include "requestworker.h"
#include "cache/fscache.h"
#include "cache/nocache.h"
#include "sandbox/sandboxfactory.h"
#include "cli.h"
#include "cliconsole.h"
@ -68,9 +69,11 @@ static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version",
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
{
std::string path = resolver.getConfig("cache_fs_dir");
if(path == "")
{
return std::make_unique<NoCache>(path);
}
return std::make_unique<FsCache>(path);
}

View File

@ -17,7 +17,7 @@ private:
Parser parser;
public:
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider) :dynamicContentFactory(templ, db, urlProvider)
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider, Session &session) :dynamicContentFactory(templ, db, urlProvider, session)
{
this->db = &db;
this->urlProvider = &urlProvider;