# coding=utf-8 import json import _mysql import time from framework import * from database import * from post import * def api(self, path_split): if len(path_split) > 2: try: self.output = api_process(self, path_split) except APIError, e: self.output = api_error("error", e.message) except UserError, e: self.output = api_error("failed", e.message) except Exception, e: import sys import traceback exc_type, exc_value, exc_traceback = sys.exc_info() detail = ["%s : %s : %s : %s" % (os.path.basename( o[0]), o[1], o[2], o[3]) for o in traceback.extract_tb(exc_traceback)] self.output = api_error("exception", str(e), str(type(e)), detail) else: self.output = api_error("error", "No method specified") def api_process(self, path_split): formdata = self.formdata ip = self.environ["REMOTE_ADDR"] t = time.time() method = path_split[2] values = {'state': 'success'} validated = False staff_account = None token = formdata.get("token") if token: staff_account = validateSession(token) if not staff_account: self.output = api_error("error", "Session expired") if staff_account: validated = True if 'session_id' in staff_account: renewSession(staff_account['session_id']) UpdateDb('UPDATE `staff` SET `lastactive` = ' + str(timestamp() ) + ' WHERE `id` = ' + staff_account['id'] + ' LIMIT 1') if validated == False: if method == 'login': if 'username' in self.formdata and 'password' in self.formdata: staff_account = verifyPasswd( formdata.get("username"), formdata.get("password")) if staff_account: session_uuid = newSession(staff_account['id']) values["token"] = session_uuid UpdateDb('DELETE FROM `logs` WHERE `timestamp` < ' + str(timestamp() - 604800)) # one week else: logAction('', 'Failed log-in. Username:'+_mysql.escape_string( self.formdata['username'])+' IP:'+self.environ["REMOTE_ADDR"]) raise APIError, "Incorrect username/password." else: raise APIError, "Bad request" else: raise APIError, "Not authenticated" else: if method == 'news': news = FetchAll( "SELECT * FROM `news` WHERE type = 1 ORDER BY `timestamp` DESC") values['news'] = news elif method == 'post': if 'id' in formdata.keys(): id = formdata.get('id') post = FetchOne("SELECT * FROM `posts` WHERE `id` = '" + _mysql.escape_string(id) + "'") values['post'] = post if 'parentid' in formdata.keys(): id = formdata.get('id') post = FetchAll("SELECT * FROM `posts` WHERE `parentid` = '" + _mysql.escape_string(id) + "'") values['posts'] = post elif method == 'reports': # /cgi/manage/reports/ignore if len(path_split) > 3: if path_split[3] == 'ignore': report_id = formdata.get("id") UpdateDb("DELETE FROM `reports` WHERE `id` = '" + _mysql.escape_string(report_id)+"'") else: values['state'] = "error" else: reports = FetchAll( "SELECT id, timestamp, timestamp_formatted, postid, parentid, link, board, INET6_NTOA(ip) AS ip, reason, INET6_NTOA(repip) AS repip FROM `reports` ORDER BY `timestamp` DESC") values['reports'] = reports elif method == 'logs': logs = FetchAll("SELECT * FROM `logs` ORDER BY `timestamp` DESC") values['logs'] = logs elif method == 'staffPosts': posts = FetchAll( "SELECT * FROM `news` WHERE type = '0' ORDER BY `timestamp` DESC") values['posts'] = posts elif method == 'login': values['token'] = token elif method == 'members': members = FetchAll( "SELECT * FROM `staff` ORDER BY lastactive DESC") values['members'] = members elif method == 'stats': report_count = FetchOne("SELECT COUNT(id) FROM `reports`") try: with open('stats.json', 'r') as f: out = json.load(f) values['stats'] = out except ValueError: values['stats'] = None raise APIError, "Stats error" values['stats']['reportCount'] = report_count['COUNT(id)'] else: raise APIError, "Invalid method" values['time'] = int(t) return json.dumps(values, sort_keys=True, separators=(',', ':')) def api_error(errtype, msg, type=None, detail=None): values = {'state': errtype, 'message': msg} if type: values['type'] = type if detail: values['detail'] = detail return json.dumps(values) def newSession(staff_id): import uuid session_uuid = uuid.uuid4().hex param_session_id = _mysql.escape_string(session_uuid) param_expires = timestamp() + Settings.SESSION_TIME param_staff_id = int(staff_id) InsertDb("INSERT INTO `session` (`session_id`, `expires`, `staff_id`) VALUES (UNHEX('%s'), %d, %d)" % (param_session_id, param_expires, param_staff_id)) return session_uuid def validateSession(session_id): cleanSessions() param_session_id = _mysql.escape_string(session_id) param_now = timestamp() session = FetchOne( "SELECT HEX(session_id) as session_id, id, username, rights, added FROM `session` " "INNER JOIN `staff` ON `session`.`staff_id` = `staff`.`id` " "WHERE `session_id` = UNHEX('%s')" % (param_session_id)) if session: return session return None def renewSession(session_id): param_session_id = _mysql.escape_string(session_id) param_expires = timestamp() + Settings.SESSION_TIME UpdateDb("UPDATE `session` SET expires = %d WHERE session_id = UNHEX('%s')" % (param_expires, param_session_id)) def deleteSession(session_id): param_session_id = _mysql.escape_string(session_id) UpdateDb("DELETE FROM `session` WHERE session_id = UNHEX('%s')" % param_session_id) def cleanSessions(): param_now = timestamp() UpdateDb("DELETE FROM `session` WHERE expires <= %d" % param_now) def logAction(staff, action): InsertDb("INSERT INTO `logs` (`timestamp`, `staff`, `action`) VALUES (" + str(timestamp()) + ", '" + _mysql.escape_string(staff) + "\', \' [API] " + _mysql.escape_string(action) + "\')") def verifyPasswd(username, passwd): import argon2 ph = argon2.PasswordHasher() param_username = _mysql.escape_string(username) staff_account = FetchOne( "SELECT * FROM staff WHERE username = '%s'" % param_username) if not staff_account: return None try: ph.verify(staff_account['password'], passwd) except argon2.exceptions.VerifyMismatchError: return None except argon2.exceptions.InvalidHash: raise UserError, "Hash obsoleto o inválido. Por favor contacte al administrador." if ph.check_needs_rehash(staff_account['password']): param_new_hash = ph.hash(staff_acount['password']) UpdateDb("UPDATE staff SET password = '%s' WHERE id = %s" % (param_new_hash, staff_account['id'])) return staff_account class APIError(Exception): pass