def update_ticket_progress(): action = request.json.get('status') ticket_id = request.json.get('ticket_id') ticket = db.tickets[ticket_id] if ticket is None: abort(400, "no such ticket exists") idx = valid_statuses.index(action) print(idx) if idx == 0: # not started EventLogger.log_status_change("Not Started", ticket_id, Helper.get_user()) ticket.update_record(started=None, completed=None) elif idx == 1: # in progress EventLogger.log_status_change("In Progress", ticket_id, Helper.get_user()) ticket.update_record(started=Helper.get_time(), completed=None) elif idx == 2: # completed EventLogger.log_status_change("Completed", ticket_id, Helper.get_user()) ticket.update_record(started=Helper.get_time() if ticket.started is None else ticket.started, completed=Helper.get_time()) return dict(ticket_id=ticket_id, status=action)
def edit_phone(contact_id=None, phone_id=None): assert contact_id is not None assert phone_id is not None # match the phone owner's id as well as the number's id row = db( (db.phone.contact_id == contact_id) & (db.phone.id == phone_id) ).select().first() if row is None: redirect(URL('edit_phones', contact_id)) contact_inf = db(db.contact.id == contact_id).select().first() if contact_inf.user_email != get_user_email(): abort(403) form = Form([Field('Phone', 'string'), Field('Kind', 'string')], record=dict(Phone=row.number, Kind=row.phone_type), deletable=False, csrf_session=session, formstyle=FormStyleBulma) form.param.sidecar.append(SPAN(" ", A("Back", _class="button", _href=URL('edit_phones', contact_id)))) if form.accepted: # insert the number for that contact number = form.vars['Phone'] phone_type = form.vars['Kind'] db((db.phone.contact_id == contact_id) & (db.phone.id == phone_id)).update( number=number, phone_type=phone_type ) redirect(URL('edit_phones', contact_id)) contact_inf = db.contact[contact_id] first_name = contact_inf.first_name last_name = contact_inf.last_name name = f"{first_name} {last_name}" return dict(form=form, name=name)
def abort_or_rediect(self, page): """ return HTTP 403 if content_type is applicaitons/json else redirects to page""" if request.content_type == "application/json": abort(403) redirect(URL(self.auth.route, page))
def abort_or_redirect(self, page, message=''): """ return HTTP 403 if 'application/json' in HTTP_ACCEPT else redirects to page""" if REX_APPJSON.search(request.headers.get("accept", "")): abort(403) redirect_next = request.fullpath if request.query_string: redirect_next = redirect_next + "?{}".format(request.query_string) redirect(URL(self.auth.route, page, vars=dict(next=redirect_next, flash=message)))
def on_request(self): """Checks the request's signature""" # extra and remove the signature from the query signature = request.query.get("_signature") if signature is None: abort(403) del request.query["_signature"] # Verifies the query keys. if signature != self.url_signer._sign(request.fullpath, request.query): abort(403)
def get_pinned_tickets(): # grabs pinned tickets for logged in user # Grab the current user's ID userID = Helper.get_user() if userID is None: abort(500, "No User ID obtained (is this possible?") # Query for pinned tickets given user ID pinnedTicketsQuery = db( db.user_pins.auth_user_id == userID).select().as_list() pinnedTickets = list(map(lambda x: x['ticket_id'], pinnedTicketsQuery)) return (dict(pinned_tickets=pinnedTickets))
def prepare_target_dir(form, target_dir): """Prepares the target directory for the new app. If should_exist is False, leaves the directory blank.""" if form["mode"] == "new": if os.path.exists(target_dir): abort(500) # already validated client side elif form["mode"] == "replace": if os.path.exists(target_dir): shutil.rmtree(target_dir) else: abort(500) # not a replacement
def on_request(self): """Checks the request's signature""" # extra and remove the signature from the query token = request.query.get("_signature") if token is None: abort(403) try: key = self.url_signer.get_url_key(request.fullpath, request.query) jwt.decode(token, key, algorithms=["HS256"]) # We remove the signature, not to pollute the request. del request.query["_signature"] except: abort(403)
def on_request(self): """Checks the request's signature""" # extra and remove the signature from the query signature = request.query.get("_signature") if signature is None: abort(403) try: h = self.url_signer.algo(self.url_signer.get_key()) signature = request.query["_signature"] sig_content = base64.b85decode( signature.encode("utf-8")).decode("utf-8") sig_dict = json.loads(sig_content) ts = sig_dict["ts"] salt = sig_dict["salt"] sig = sig_dict["sig"] h.update( self.url_signer.get_info_to_sign(request.fullpath, request.query, ts, salt)) computed_sig = base64.b85encode(h.digest()).decode("utf-8") if sig != computed_sig: abort(403) # We remove the signature, not to pollute the request. del request.query["_signature"] # Checks the expiration time. if self.url_signer.lifespan is not None: if float(ts) + self.url_signer.lifespan < time.time(): abort(403) except: abort(403)
def new_app(): form = request.json # Directory for zipped assets assets_dir = os.path.join(os.path.dirname(py4web.__file__), "assets") app_name = form["name"] target_dir = safe_join(FOLDER, app_name) if form["type"] == "minimal": source = os.path.join(assets_dir, "py4web.app._minimal.zip") source_dir = safe_join(FOLDER, "_minimal") prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form["type"] == "scaffold": source = os.path.join(assets_dir, "py4web.app._scaffold.zip") source_dir = safe_join(FOLDER, "_scaffold") prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form["type"] == "web": prepare_target_dir(form, target_dir) source = form["source"] if source.endswith(".zip"): # install from the web (zip file) res = requests.get(source) mem_zip = io.BytesIO(res.content) zfile = zipfile.ZipFile(mem_zip, "r") zfile.extractall(target_dir) zfile.close() elif source.endswith(".git"): # clone from a git repo process = subprocess.Popen( ["git", "clone", source, form["name"]], cwd=FOLDER ) process.communicate() if process.returncode != 0: abort(500) elif form["type"] == "upload": print(request.files.keys()) prepare_target_dir(form, target_dir) source_stream = io.BytesIO(base64.b64decode(form["file"])) zfile = zipfile.ZipFile(source_stream, "r") zfile.extractall(target_dir) zfile.close() else: abort(500) settings = os.path.join(target_dir, "settings.py") if os.path.exists(settings): with open(settings) as fp: data = fp.read() data = data.replace("<session-secret-key>", str(uuid.uuid4())) with open(settings, "w") as fp: fp.write(data) Reloader.import_app(app_name) return {"status": "success"}
def get_helppage(pagename: str): print("GOT HELPFILE REQUEST FOR PAGE " + pagename) VALID_PAGENAMES = {'usage': 'USAGE.md', 'attribution': 'ATTRIBUTION.md'} if pagename not in VALID_PAGENAMES: abort(403, "Not a valid help page.") help_file_path = settings.APP_FOLDER + "/" + VALID_PAGENAMES[pagename] if not path.exists(help_file_path): abort(404, "Help file not found; this is an error") helpFileContents = str() # Otherwise read and serve the file with open(help_file_path, 'r') as file: helpFileContents = file.read() return helpFileContents
def abort_or_redirect(self, page, message=""): """ return HTTP 403 if 'application/json' in HTTP_ACCEPT else redirects to page""" if re.search(REGEX_APPJSON, request.headers.get("accept", "")): abort(403) redirect_next = request.fullpath if request.query_string: redirect_next = redirect_next + "?{}".format(request.query_string) self.auth.flash.set(message) redirect( URL( self.auth.route, page, vars=dict(next=redirect_next), use_appname=self.auth.param.use_appname_in_redirects, ))
def pin_ticket(): ticketID = request.json.get("ticket_id") if ticketID is None: abort(400, "Ticket ID to pin not provided") userID = Helper.get_user() if userID is None: abort(500, "No User ID obtained (is this possible?)") potentialPinQuery = db((db.user_pins.auth_user_id == userID) & \ (db.user_pins.ticket_id == ticketID)) # the query pin = potentialPinQuery.select() # fetch query from db if not pin: # Create a pin record db.user_pins.insert(auth_user_id=userID, ticket_id=ticketID) else: # Delete it potentialPinQuery.delete() return "ok"
def save(path, reload_app=True): """Saves a file""" app_name = path.split("/")[0] path = safe_join(FOLDER, path) or abort() with open(path, "wb") as myfile: myfile.write(request.body.read()) if reload_app: Reloader.import_app(app_name) return {"status": "success"}
def delete_phone(contact_id=None, phone_id=None): assert contact_id is not None assert phone_id is not None # match the phone owner's id as well as the number's id row = db( (db.phone.contact_id == contact_id) & (db.phone.id == phone_id) ).select().first() # row.id is the id of the phone number being targeted if row.id is None: redirect(URL('edit_phones', contact_id)) contact_inf = db(db.contact.id == contact_id).select().first() if contact_inf.user_email != get_user_email(): abort(403) db( db.phone.id == row.id ).delete() redirect(URL('edit_phones', contact_id))
def new_app(): form = request.json # Directory for zipped assets assets_dir = os.path.join(os.path.dirname(py4web.__file__), "assets") target_dir = safe_join(FOLDER, form["name"]) if form["type"] == "minimal": source = os.path.join(assets_dir, "py4web.app._minimal.zip") source_dir = safe_join(FOLDER, "_minimal") prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form["type"] == "scaffold": source = os.path.join(assets_dir, "py4web.app._scaffold.zip") source_dir = safe_join(FOLDER, "_scaffold") prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form["type"] == "web": prepare_target_dir(form, target_dir) source = form["source"] if source.endswith(".zip"): # install from the web (zip file) res = requests.get(source) mem_zip = io.BytesIO(res.content) zfile = zipfile.ZipFile(mem_zip, "r") zfile.extractall(target_dir) zfile.close() elif source.endswith(".git"): # clone from a git repo process = subprocess.Popen( ["git", "clone", source, form["name"]], cwd=FOLDER ) process.communicate() if process.returncode != 0: abort(500) elif form["type"] == "upload": prepare_target_dir(form, target_dir) source_stream = io.BytesIO(base64.b64decode(form["file"])) zfile = zipfile.ZipFile(source_stream, "r") zfile.extractall(target_dir) zfile.close() else: abort(500) return {"status": "success"}
def compile_py(): fp = request.json.get('fp') w23p_app = request.json.get('w23p_app') code = request.json.get('code') fp = fp[0] == '/' and fp[1:] or fp fp = os.path.join(APPS_FOLDER, w23p_app, fp) if not os.path.isfile(fp): #raise HTTP(404, web2py_error = '`%s` not found' % fp) abort(404, '`%s` not found' % fp) if not fp.endswith('.py'): raise abort(400, '`%s` is not python file' % fp) code_raw = code if code is not None else fs2json.safe_read(fp) code = code_raw.rstrip().replace('\r\n', '\n') + '\n' highlight = None import _ast error = None try: compile(code, fp, "exec", _ast.PyCF_ONLY_AST) except Exception as e: # offset calculation is only used for textarea (start/stop) start = sum([len(line) + 1 for l, line in enumerate(code_raw.split("\n")) if l < e.lineno - 1]) if e.text and e.offset: offset = e.offset - (len(e.text) - len( e.text.splitlines()[-1])) else: offset = 0 highlight = {'start': start, 'end': start + offset + 1, 'lineno': e.lineno, 'offset': offset} try: ex_name = e.__class__.__name__ except: ex_name = 'unknown exception!' error = dict(line = e.lineno, col = offset, message = ex_name) return dict(err = error )
def new_app(): form = request.json # Directory for zipped assets assets_dir = os.path.join(os.path.dirname(py4web.__file__), 'assets') target_dir = safe_join(FOLDER, form['name']) if form['type'] == 'minimal': source = os.path.join(assets_dir,'py4web.app._minimal.zip') source_dir = safe_join(FOLDER, '_minimal') prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form['type'] == 'scaffold': source = os.path.join(assets_dir,'py4web.app._scaffold.zip') source_dir = safe_join(FOLDER, '_scaffold') prepare_target_dir(form, target_dir) install_by_unzip_or_treecopy(source, source_dir, target_dir) elif form['type'] == 'web': prepare_target_dir(form, target_dir) source = form['source'] if source.endswith('.zip'): # install from the web (zip file) res = requests.get(source) mem_zip = io.BytesIO(res.content) zfile = zipfile.ZipFile(mem_zip, 'r') zfile.extractall(target_dir) zfile.close() elif source.endswith('.git'): # clone from a git repo if subprocess.call(['git', 'clone', source, form['name']]): abort(500) elif form['type'] == 'upload': prepare_target_dir(form, target_dir) source_stream = io.BytesIO(base64.b64decode(form['file'])) zfile = zipfile.ZipFile(source_stream, 'r') zfile.extractall(target_dir) zfile.close() else: abort(500) return {'status':'success'}
def new_app(): form = request.json target_dir = safe_join(FOLDER, form['name']) if os.path.exists(target_dir): if form['mode'] == 'new': abort(500) # already validated client side elif form['mode'] == 'replace': shutil.rmtree(target_dir) elif form['type'] != 'web' and not form['source'].endswith('.git'): os.mkdir(target_dir) assets_dir = os.path.join(os.path.dirname(py4web.__file__), 'assets') source = None if form['type'] == 'minimal': source = os.path.join(assets_dir, 'py4web.app._minimal.zip') elif form['type'] == 'scaffold': source = os.path.join(assets_dir, 'py4web.app._scaffold.zip') elif form['type'] == 'web': source = form['source'] elif form['type'] == 'upload': source_stream = io.BytesIO(base64.b64decode(form['file'])) else: abort(500) # TODO catch and report better errors below if form['type'] == 'upload': zip = zipfile.ZipFile(source_stream, 'r') zip.extractall(target_dir) zip.close() elif not '://' in source: # install from a local asset (zip) file zip = zipfile.ZipFile(source, 'r') zip.extractall(target_dir) zip.close() elif source.endswith('.zip'): # install from the web (zip file) res = requests.get(source) mem_zip = io.BytesIO(res.content) zipfile.ZipFile(mem_zip, 'r') zip.extractall(target_dir) zip.close() elif source.endswith('.git'): # clone from a git repo if subprocess.call(['git', 'clone', source, form['name']]): abort(500) else: abort(400) return {'status': 'success'}
def edit_ticket(): ticket_id = request.json.get('id') title = request.json.get('title') text = request.json.get('text') tag_list = request.json.get('tag_list') due_date = request.json.get('due_date') # TODO: implement due dates here ticket = db.tickets[ticket_id] if ticket is None: abort(400, "could not find specified ticket") if type(tag_list) is not list and tag_list is not None: print("wrong type for tag_list") abort(500, "wrong type for tag_list") if type(title) is not str: print("wrong values for title or text") abort(500, "wrong values for title and text") ticket.update_record(ticket_title=title.strip(), ticket_text=text.strip() if text else "", due=parse(due_date) if due_date else None) current_tickets = Helper.get_ticket_tags_by_id(ticket_id) # tag ids should be unique so this should not cause an error tag_set = set() for tag in tag_list: tag_id = tag.get('id') if tag_id is None: tag_id = db(db.global_tag).insert(tag_name=tag.get('tag_name')) tag_set.add(tag_id) current_set = {tag.get('id') for tag in current_tickets } if current_tickets is not None else set() # yay inefficient tag update, set difference does not work on sets full of dicts for tag_id in current_set.difference(tag_set): db((db.ticket_tag.ticket_id == ticket_id) & (db.ticket_tag.tag_id == tag_id)).delete() for tag_id in tag_set.difference(current_set): db.ticket_tag.insert(ticket_id=ticket_id, tag_id=tag_id) return "ok"
def abort_or_rediect(self, page): if request.content_type == 'application/json': abort(403) redirect(URL(self.auth.route + page))
def abort_response(): abort(400)
def on_request(self): user = self.session.get("user") if not user or not user.get("id"): abort(403)
def delete(path): """Deletes a file""" fullpath = safe_join(FOLDER, path) or abort() recursive_unlink(fullpath) return {"status": "success"}
def load_bytes(path): """Loads a binary file""" path = safe_join(FOLDER, path) or abort() return open(path, "rb").read()
def load(path): """Loads a text file""" path = safe_join(FOLDER, path) or abort() content = open(path, "rb").read().decode("utf8") return {"payload": content, "status": "success"}
def save(path): """Saves a file""" path = safe_join(FOLDER, path) or abort() with open(path, 'wb') as myfile: myfile.write(request.body.read()) return {'status':'success'}
def on_request(self): user = self.session.get('user') if not user or not user.get('id'): abort(403)
def action(self, path, method, get_vars, post_vars, env=None): """action that handles all the HTTP requests for Auth""" env = env or {} if path.startswith("plugin/"): parts = path.split("/", 2) plugin = self.plugins.get(parts[1]) if plugin: return plugin.handle_request( self, parts[2], request.query, request.json ) else: abort(404) if path.startswith("api/"): data = {} if method == "GET": # Should we use the username? if path == "api/use_username": return {"use_username": self.use_username} if path == "api/config": fields = [ dict(name=f.name, type=f.type) for f in self.db.auth_user if f.type in ["string", "bool", "integer", "float"] and f.writable and f.readable ] return { "allowed_actions": self.allowed_actions, "plugins": ["local"] + [key for key in self.plugins], "fields": fields, } # Otherwise, we assume the user exists. user = self.get_user(safe=True) if not user: data = self._error("not authorized", 401) if path == "api/profile": return {"user": user} elif method == "POST" and self.db: vars = dict(post_vars) user = self.get_user(safe=False) if path == "api/register": data = self.register(vars, send=True).as_dict() elif path == "api/login": # Prioritize PAM or LDAP logins if enabled if "pam" in self.plugins or "ldap" in self.plugins: plugin_name = "pam" if "pam" in self.plugins else "ldap" username, password = vars.get("email"), vars.get("password") check = self.plugins[plugin_name].check_credentials( username, password ) if check: data = { "username": username, # "email": username + "@localhost", "sso_id": plugin_name + ":" + username, } # and register the user if we have one, just in case if self.db: data = self.get_or_register_user(data) self.session["user"] = {"id": data["id"]} self.session["recent_activity"] = calendar.timegm( time.gmtime() ) self.session["uuid"] = str(uuid.uuid1()) else: data = self._error("Invalid Credentials") # Else use normal login else: user, error = self.login(**vars) if user: self.session["user"] = {"id": user.id} self.session["recent_activity"] = calendar.timegm( time.gmtime() ) self.session["uuid"] = str(uuid.uuid1()) user = { f.name: user[f.name] for f in self.db.auth_user if f.readable } data = {"user": user} else: data = self._error(error) elif path == "api/request_reset_password": if not self.request_reset_password(**vars): data = self._error("invalid user") elif path == "api/reset_password": if not self.reset_password( vars.get("token"), vars.get("new_password") ): data = self._error("invalid token, request expired") elif user and path == "api/logout": self.session["user"] = None elif user and path == "api/unsubscribe": self.session["user"] = None self.gdpr_unsubscribe(user, send=True) elif user and path == "api/change_password": data = self.change_password( user, vars.get("new_password"), vars.get("old_password") ) elif user and path == "api/change_email": data = self.change_email( user, vars.get("new_email"), vars.get("password") ) elif user and path == "api/profile": data = self.update_profile(user, **vars) else: data = {"status": "error", "message": "undefined"} if not "status" in data and data.get("errors"): data.update(status="error", message="validation errors", code=401) elif "errors" in data and not data["errors"]: del data["errors"] data["status"] = data.get("status", "success") data["code"] = data.get("code", 200) return data elif path == "logout": self.session.clear() # Somehow call revoke for active plugin elif path == "verify_email" and self.db: token = get_vars.get("token") if self.verify_email(token): next = b16d(token.split("/")[1]) redirect( next or URL( "auth", "email_verified", use_appname=self.use_appname_in_redirects, ) ) else: redirect( URL( "auth", "token_expired", use_appname=self.use_appname_in_redirects, ) ) env["path"] = path return Template("auth.html").transform(env)
def action(self, path, method, get_vars, post_vars): if path.startswith('plugin/'): parts = path.split('/', 2) plugin = self.plugins.get(parts[1]) if plugin: return plugin.handle_request(self, parts[2], request.query, request.json) else: abort(404) if path.startswith('api/'): data = {} if method == 'GET': user = self.get_user(safe=True) if not user: data = self._error('not authoried', 401) if path == 'api/profile': return {'user': user} elif method == 'POST' and self.db: vars = dict(post_vars) user = self.get_user(safe=False) if path == 'api/register': data = self.register(vars, send=True).as_dict() elif path == 'api/login': # Prioritize PAM or LDAP logins if enabled if 'pam' in self.plugins or 'ldap' in self.plugins: plugin_name = 'pam' if 'pam' in self.plugins else 'ldap' username, password = vars.get('email'), vars.get( 'password') check = self.plugins[plugin_name].check_credentials( username, password) if check: data = { 'username': username, 'email': username + '@localhost', 'sso_id': plugin_name + ':' + username, } # and register the user if we have one, just in case if self.db: data = self.get_or_register_user(data) else: data = self._error('Invalid Credentials') # Else use normal login else: user, error = self.login(**vars) if user: self.session['user'] = {'id': user.id} user = { f.name: user[f.name] for f in self.db.auth_user if f.readable } data = {'user': user} else: data = self._error(error) elif path == 'api/request_reset_password': if not self.request_reset_password(**vars): data = self._error('invalid user') elif path == 'api/reset_password': if not self.reset_password(vars.get('token'), vars.get('new_password')): data = self._error('invalid token, request expired') elif user and path == 'api/logout': self.session['user'] = None elif user and path == 'api/unsubscribe': self.session['user'] = None self.gdpr_unsubscribe(user, send=True) elif user and path == 'api/change_password': data = self.change_password(user, vars.get('new_password'), vars.get('password')) elif user and path == 'api/change_email': data = self.change_email(user, vars.get('new_email'), vars.get('password')) elif user and path == 'api/profile': data = self.update_profile(user, **vars) else: data = {'status': 'error', 'message': 'undefined'} if not 'status' in data and data.get('errors'): data.update(status='error', message='validation errors', code=401) elif 'errors' in data and not data['errors']: del data['errors'] data['status'] = data.get('status', 'success') data['code'] = data.get('code', 200) return data elif path == 'logout': self.session['user'] = None # Somehow call revoke for active plugin elif path == 'verify_email' and self.db: if self.verify_email(get_vars.get('token')): redirect(URL('auth/email_verified')) else: redirect(URL('auth/token_expired')) return Template('auth.html').transform({'path': path})