d27769317be7f92e199f69d75b10a7421eecf770 max Sat Sep 26 21:57:15 2026 -0700 hgGeneGraph: require a form submission and a checked answer to file a feedback report diff --git src/hg/hgGeneGraph/hgGeneGraph src/hg/hgGeneGraph/hgGeneGraph index 7ee374f979d..0f87b36bf9d 100755 --- src/hg/hgGeneGraph/hgGeneGraph +++ src/hg/hgGeneGraph/hgGeneGraph @@ -19,31 +19,31 @@ # dark blue, dashed = only low-throughput data # dark blue, thickness = low-throughput data + text # dark blue + dashed = only pathway data # code review # - os.system is not a security risk here, no variables go into the cmd line # - mysql statements are not escaped, instead all CGI vars are checked for non-alpha letters # hgFixed tables required for this script: ggLink (main table with gene-gene links), # ggLinkEvent (details about link), ggEventDb (details about links from databases), # ggEventText (details about links from text mining), ggDoc (details about documents for ggEventText) # ggGeneName (symbols), ggGeneClass (HPRD/Panther class) # these are default python modules on python 2.7, no errors expected here -import sys, cgi, os, string, urllib.request, urllib.parse, urllib.error, operator, hashlib, html +import sys, cgi, os, string, urllib.request, urllib.parse, urllib.error, operator, hashlib, html, random from sys import exit from collections import defaultdict, namedtuple from os.path import * #These two lines should be commented out whenever committing this file into git. #They activate stack traces to stdout which can in certain cases reveal the mysql password, #when there is a Mysql connection problem. #import cgitb #cgitb.enable() # import the UCSC-specific library sys.path.append(join(dirname(__file__), "pyLib")) try: from hgLib3 import cgiArgs, cgiSetup, cgiString, printContentType, printMenuBar, \ sqlConnect, sqlQuery, errAbort, cfgOption, runCmd, cgiGetAll, printHgcHeader, \ @@ -1880,57 +1880,69 @@ print("<h3>Interaction %s - %s</h3>" % (causeGene, themeGene)) conn = sqlConnect(GGDB) rows = queryEventText(conn, causeGene, themeGene) print ("<p>") print ("Thank you for reporting errors, e.g.") print("<ul><li>not true (e.g. the authors state that the interaction does not happen )</li>") print("<li>text mining errors (the authors did not say anything about this particular interaction)</li>") print("</ul>") print("This makes it easier for us to improve the text mining system or database imports.<br>") print("You can leave your email address in the comment if you want to give us the possibility to comment.<P>") print ("Optional comment: <br>") - print(('<FORM method="GET" action="%s">' % basename(__file__))) + print(('<FORM method="POST" action="%s">' % basename(__file__))) print(('<INPUT TYPE="HIDDEN" NAME="remove" VALUE="%s"></INPUT>' % param)) + # A short arithmetic question, checked back in removeInteraction() against the salted + # hash of its answer, keeps this write-capable form from being submitted from anywhere + # but this page. + numA = random.randint(1, 9) + numB = random.randint(1, 9) + print(('<INPUT TYPE="HIDDEN" NAME="captchaHash" VALUE="%s"></INPUT>' % saltedHash(str(numA+numB)))) print ('<TEXTAREA rows="6" cols="50" name="comment"></TEXTAREA>') print ("<P>") + print ('What is %d + %d? <INPUT TYPE="TEXT" NAME="captcha" SIZE="4"></INPUT></P>' % (numA, numB)) print ('<INPUT TYPE="SUBMIT" name="submit" value="Report Interaction as inaccurate"></INPUT>') print ('</FORM>') #printMsrNlpRows(rows) showSnipsLink(conn, causeGene, themeGene) #def namedtuple_factory(cursor, row): #""" #used as a function pointer to sqlite to have it return structs with names and not just arrays #""" #fields = [col[0] for col in cursor.description] #Row = collections.namedtuple("Row", fields) #return Row(*row) #def openSqlite(dbName): #" opens sqlite database and have it return structs (=namedtuples) " #tryCount = retries #con = None #con = sqlite3.connect(dbName, timeout=20) #con.row_factory = namedtuple_factory ##con.row_factory = sqlite3.Row -def removeInteraction(param, captcha, comment): +def removeInteraction(param, captcha, captchaHash, comment): + if os.environ.get("REQUEST_METHOD") != "POST": + errAbort("This action requires a form submission, not a link.") + if captcha is None or captchaHash is None or saltedHash(captcha.strip()) != captchaHash: + errAbort("The answer to the arithmetic question was not correct. Please go back and try again.") + fields = param.split(":") if len(fields)!=2: errAbort( "illegal CGI parameter") causeGene, themeGene = fields ip = html.escape(os.environ["REMOTE_ADDR"]) conn = hConnectCentral() if not sqlTableExists(conn, "ggFeedback"): # sqlQuery() reads cursor.description, which is None after a statement that returns no # rows, so it cannot run a CREATE sqlUpdate(conn, "CREATE TABLE ggFeedback (causeGene varchar(255), themeGene varchar(255), pmid varchar(255), " \ "comment varchar(10000), time TIMESTAMP, ip varchar(255), INDEX allIdx (causeGene, themeGene, pmid));") # name the columns: the positional version put NOW() into "comment" and the comment text @@ -2006,32 +2018,33 @@ print("<li>%s: %d" % (db, len(pairSet))) print("</ul>") def htmlMiddle(): " print html middle part " sys.stdout.flush() flag = getCgiVar("flag") if flag!=None: flagInteraction(flag) exit(0) remove = getCgiVar("remove") if remove!=None: captcha = getCgiVar("captcha") + captchaHash = getCgiVar("captchaHash", maxLen=40) comment = getCgiVar("comment", allowAnyChar=True, maxLen=10000) - removeInteraction(remove, captcha, comment) + removeInteraction(remove, captcha, captchaHash, comment) exit(0) docId = getCgiVar("docId") if docId!=None: showDoc(docId) exit(0) link = getCgiVar("link") if link!=None: showLink(link) exit(0) page = getCgiVar("page") if page=="stats": showStats()