/* ** Copyright (c) 2012 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to deal with moderator actions for ** Wiki and Tickets. */ #include "config.h" #include "moderate.h" #include /* ** Create a table to represent pending moderation requests, if the ** table does not already exist. */ void moderation_table_create(void){ db_multi_exec( "CREATE TABLE IF NOT EXISTS repository.modreq(\n" " objid INTEGER PRIMARY KEY,\n" /* Record pending approval */ " attachRid INT,\n" /* Object attached */ " tktid TEXT\n" /* Associated ticket id */ ");\n" ); } /* ** Return TRUE if the modreq table exists */ int moderation_table_exists(void){ return db_table_exists("repository", "modreq"); } /* ** Return TRUE if the object specified is being held for moderation. */ int moderation_pending(int rid){ static Stmt q; int rc; if( rid==0 || !moderation_table_exists() ) return 0; db_static_prepare(&q, "SELECT 1 FROM modreq WHERE objid=:objid"); db_bind_int(&q, ":objid", rid); rc = db_step(&q)==SQLITE_ROW; db_reset(&q); return rc; } /* ** If the rid object is being held for moderation, write out ** an "awaiting moderation" message and return true. ** ** If the object is not being held for moderation, simply return ** false without generating any output. */ int moderation_pending_www(int rid){ int pending = moderation_pending(rid); if( pending ){ #if 0 if( moderation_user_could(rid, 1, 0) ){ /* It would be nice to emit a link to the appropriate page to ** approve/reject the moderation, but for that we need ** artifact-type-dependent info and links. That's complicated by ** the fact that deriving whether rid refers to an attachment or ** an attachment target is apparently tricky because of how ** attachments are recorded in the event table. */ @ (\ @Awaiting Moderator Approval) }else #endif { @ (Awaiting Moderator Approval) } } return pending; } /* ** Return TRUE if there any pending moderation requests. */ int moderation_needed(void){ if( !moderation_table_exists() ) return 0; return db_exists("SELECT 1 FROM modreq"); } /* ** Check to see if the object identified by RID is used for anything. */ static int object_used(int rid){ static const char *const aTabField[] = { "modreq", "attachRid", "mlink", "mid", "mlink", "fid", "tagxref", "srcid", "tagxref", "rid", }; int i; for(i=0; iAll Pending Moderation Requests if( moderation_table_exists() ){ blob_init(&sql, timeline_query_for_www(), -1); blob_append_sql(&sql, " AND event.objid IN (SELECT objid FROM modreq)" " ORDER BY event.mtime DESC" ); db_prepare(&q, "%s", blob_sql_text(&sql)); www_print_timeline(&q, 0, 0); db_finalize(&q); } style_finish_page(); } /* ** Disapproves any entries in the modreq table which belong to any ** user whose name is no longer found in the user table. This is only ** intended to be called after user deletion via /setup_uedit. ** ** To figure out whether a name exists it cross-references ** coalesce(event.euser, event.user) with user.login, limiting the ** selection to event entries where objid matches an entry in the ** modreq table. ** ** This is a no-op if called without g.perm.Admin permissions or if ** moderation_table_exists() returns false. */ void moderation_disapprove_for_missing_users(){ Stmt q; if( !g.perm.Admin || !moderation_table_exists() ){ return; } db_begin_transaction(); db_prepare(&q, "SELECT objid FROM event WHERE objid IN " "(SELECT objid FROM modreq) " "AND coalesce(euser,user) NOT IN " "(SELECT login FROM user)" ); while( db_step(&q)==SQLITE_ROW ){ int const objid = db_column_int(&q, 0); moderation_disapprove(objid); } db_finalize(&q); setup_incr_cfgcnt(); db_end_transaction(0); } /* ** Returns true if the current user could ostensibly moderate the blob ** refered to by rid, irrespective of whether that object is currently ** pending moderation. If rid is not an event.objid value then this ** returns 0. ** ** If bMayDeny is true then a matching user is permitted to moderate a ** decline by not an approval. Pass 1 here if true should be returned ** if the current user matches the artifact. When passing false, it ** will only return true for users who have explicit moderation ** permissions. The purpose of this is to exclude pending-moderation ** from the current user in some contexts but not others. ** ** zWho is an optional user name to consider for ownership of an ** artifact, as compared to the artifact's matching event.(euser,user) ** fields. If 0 then it defaults to login_name(). This is strictly a ** name comparison - it does not inspect zWho's repo-level ** permissions. ** ** Design issue: since this gets its info from the event table, it ** cannot unambiguously distinguish between an attachment-capable ** artifact type and attachments to one. Attachment events are encoded ** with type=X, where X is the same as the artifact type to which the ** attachment was applied. ** ** The moderation rules applied here are: ** ** - Admins may always moderate. This is a fast path which bypasses ** artifact lookup. For non-admins, we look for a record in the ** event table. ** ** - Forum, Wiki, and Ticket moderators may always moderate a matching ** artifact. If bMayDeny is true then an artifact's owner, even if ** not a moderator, may moderate it. i.e. a non-moderator owner can ** reject their pending-moderation objects but they may not approve ** them. ** ** - Returns 0 for all other artifact types except that it will always ** return true for admins because that check skips looking at the ** db. ** */ int moderation_user_could(int rid, int bMayDeny, const char *zWho){ static Stmt q; int rc = 0; if( g.perm.Admin ) return g.perm.Admin; if( !q.pStmt ){ db_static_prepare( &q, "SELECT coalesce(euser,user)=:user, type FROM event " "WHERE objid=:rid" ); } db_bind_int(&q, ":rid", rid); db_bind_text(&q, ":user", zWho ? zWho : login_name()); if( SQLITE_ROW==db_step(&q) ){ const int bIsOwner = db_column_int(&q, 0); const char *zType = db_column_text(&q, 1); switch( zType ? zType[0] : 0 ){ case 'f': rc = g.perm.ModForum || (bIsOwner && bMayDeny); break; case 't': rc = g.perm.ModTkt || (bIsOwner && bMayDeny); break; case 'w': rc = g.perm.ModWiki || (bIsOwner && bMayDeny); break; /* case 'e': Technotes and their attachments are not subject ** to moderation. */ default: break; } } db_reset(&q); return rc; } /* ** COMMAND: test-user-could-moderate ** ** Usage: %fossil test-user-could-moderate ?-deny? user-name ...artifactNames ** ** Tests whether a given user would have the ability to moderate ** the given artifacts. The -deny flag indicates that the check should ** permit moderation if the artifact is owned by the same user. */ void test_moderation_user_could_cmd(void){ const char *zWho; const int bMayDeny = find_option("deny",0,0) != 0; char * zCap; int i; db_find_and_open_repository(0,0); verify_all_options(); if( g.argc<4 ){ usage("user-name artifact-names..."); } zWho = g.zLogin = g.argv[2]; zCap = db_text( 0, "SELECT cap FROM user WHERE login=%Q", zWho ); if( !zCap ){ fossil_fatal("Cannot determine capabilities of user %s", zWho); } login_set_capabilities(zCap, 0); fossil_print("User: %s\nCaps: %s\n", zWho, zCap); fossil_free(zCap); for( i = 3; i < g.argc; ++i ){ const char * zArg = g.argv[i]; int rid = symbolic_name_to_rid(zArg, "*"); int may; if( rid<=0 ){ fossil_fatal("Cannot resolve name: %s", zArg); } may = moderation_user_could(rid, bMayDeny, zWho); fossil_print("%s\t\t=> %d\t=> %s\n", zArg, rid, may ? "yes" : "no"); } }