async def patch(self, request: Request) -> JSONResponse: """Updates form by ID.""" try: data = await request.json() except json.decoder.JSONDecodeError: return JSONResponse("Expected a JSON body.", 400) form_id = request.path_params["form_id"].lower() await discord.verify_edit_access(form_id, request) if raw_form := await request.state.db.forms.find_one({"_id": form_id}): if "_id" in data or "id" in data: if (data.get("id") or data.get("_id")) != form_id: return JSONResponse({"error": "locked_field"}, status_code=400) # Build Data Merger merge_strategy = [(dict, ["merge"])] merger = deepmerge.Merger(merge_strategy, ["override"], ["override"]) # Merge Form Data updated_form = merger.merge(raw_form, data) try: form = Form(**updated_form) except ValidationError as e: return JSONResponse(e.errors(), status_code=422) await request.state.db.forms.replace_one({"_id": form_id}, form.dict()) return JSONResponse(form.dict())
async def get(self, request: Request) -> JSONResponse: """Returns single form information by ID.""" form_id = request.path_params["form_id"].lower() try: await discord.verify_edit_access(form_id, request) admin = True except discord.FormNotFoundError: if not constants.PRODUCTION and form_id == EMPTY_FORM.id: # Empty form to help with authentication in development. return JSONResponse(EMPTY_FORM.dict(admin=False)) raise except discord.UnauthorizedError: admin = False filters = {"_id": form_id} if not admin: filters["features"] = {"$in": ["OPEN", "DISCOVERABLE"]} form = Form(**await request.state.db.forms.find_one(filters)) if not admin: form = filter_unittests(form) return JSONResponse(form.dict(admin=admin))
async def post(self, request: Request) -> JSONResponse: """Create a new form.""" form_data = await request.json() # Verify Webhook try: # Get url from request webhook = form_data[WebHook.__name__.lower()] if webhook is not None: url = webhook[WebHook.URL.value] # Validate URL validation = await validate_hook_url(url) if validation: return JSONResponse(validation.errors(), status_code=422) except KeyError: pass form = Form(**form_data) if await request.state.db.forms.find_one({"_id": form.id}): return JSONResponse({"error": "id_taken"}, status_code=400) await request.state.db.forms.insert_one(form.dict(by_alias=True)) return JSONResponse(form.dict())
async def get(self, request: Request) -> JSONResponse: """Return a list of all forms to authenticated users.""" forms = [] cursor = request.state.db.forms.find() for form in await cursor.to_list(None): forms.append(Form(**form)) # For converting _id to id # Covert them back to dictionaries forms = [form.dict() for form in forms] return JSONResponse(forms)
async def get(self, request: Request) -> JSONResponse: """List all discoverable forms that should be shown on the homepage.""" forms = [] cursor = request.state.db.forms.find({ "features": "DISCOVERABLE" }).sort("name") # Parse it to Form and then back to dictionary # to replace _id with id for form in await cursor.to_list(None): forms.append(Form(**form)) forms = [form.dict(admin=False) for form in forms] # Return an empty form in development environments to help with authentication. if not forms and not constants.PRODUCTION: forms.append(EMPTY_FORM.dict(admin=False)) return JSONResponse(forms)
async def submit(self, request: Request) -> JSONResponse: """Helper method for handling submission logic.""" data = await request.json() data["timestamp"] = None if form := await request.state.db.forms.find_one({ "_id": request.path_params["form_id"], "features": "OPEN" }): form = Form(**form) response = data.copy() response["id"] = str(uuid.uuid4()) response["form_id"] = form.id if constants.FormFeatures.DISABLE_ANTISPAM.value not in form.features: ip_hash_ctx = hashlib.md5() ip_hash_ctx.update( request.headers.get("Cf-Connecting-IP", request.client.host).encode()) ip_hash = binascii.hexlify(ip_hash_ctx.digest()) user_agent_hash_ctx = hashlib.md5() user_agent_hash_ctx.update( request.headers["User-Agent"].encode()) user_agent_hash = binascii.hexlify( user_agent_hash_ctx.digest()) async with httpx.AsyncClient() as client: query_params = { "secret": constants.HCAPTCHA_API_SECRET, "response": data.get("captcha") } r = await client.post(HCAPTCHA_VERIFY_URL, params=query_params, headers=HCAPTCHA_HEADERS) r.raise_for_status() captcha_data = r.json() response["antispam"] = { "ip_hash": ip_hash.decode(), "user_agent_hash": user_agent_hash.decode(), "captcha_pass": captcha_data["success"] } if constants.FormFeatures.REQUIRES_LOGIN.value in form.features: if request.user.is_authenticated: response["user"] = request.user.payload response["user"]["admin"] = request.user.admin if (constants.FormFeatures.COLLECT_EMAIL.value in form.features and "email" not in response["user"]): return JSONResponse({"error": "email_required"}, status_code=400) else: return JSONResponse({"error": "missing_discord_data"}, status_code=400) missing_fields = [] for question in form.questions: if question.id not in response["response"]: if not question.required: response["response"][question.id] = None else: missing_fields.append(question.id) if missing_fields: return JSONResponse( { "error": "missing_fields", "fields": missing_fields }, status_code=400) try: response_obj = FormResponse(**response) except ValidationError as e: return JSONResponse(e.errors(), status_code=422) # Run unittests if needed if any("unittests" in question.data for question in form.questions): unittest_results = await execute_unittest(response_obj, form) failures = [] status_code = 422 for test in unittest_results: response_obj.response[test.question_id] = { "value": response_obj.response[test.question_id], "passed": test.passed } if test.return_code == 0: failure_names = [] if test.passed else test.result.split( ";") elif test.return_code == 5: failure_names = ["Could not parse user code."] elif test.return_code == 6: failure_names = ["Could not load user code."] else: failure_names = ["Internal error."] response_obj.response[ test.question_id]["failures"] = failure_names # Report a failure on internal errors, # or if the test suite doesn't allow failures if not test.passed: allow_failure = (form.questions[test.question_index]. data["unittests"]["allow_failure"]) # An error while communicating with the test runner if test.return_code == 99: failures.append(test) status_code = 500 elif not allow_failure: failures.append(test) if len(failures): return JSONResponse( { "error": "failed_tests", "test_results": [test._asdict() for test in failures] }, status_code=status_code) await request.state.db.responses.insert_one( response_obj.dict(by_alias=True)) tasks = BackgroundTasks() if constants.FormFeatures.WEBHOOK_ENABLED.value in form.features: if constants.FormFeatures.REQUIRES_LOGIN.value in form.features: request_user = request.user else: request_user = None tasks.add_task(self.send_submission_webhook, form=form, response=response_obj, request_user=request_user) if constants.FormFeatures.ASSIGN_ROLE.value in form.features: tasks.add_task(self.assign_role, form=form, request_user=request.user) return JSONResponse( { "form": form.dict(admin=False), "response": response_obj.dict() }, background=tasks)
constants.FormFeatures.DISCOVERABLE.value, constants.FormFeatures.OPEN.value, constants.FormFeatures.REQUIRES_LOGIN.value ] __QUESTION = Question( id="description", name="Check your cookies after pressing the button.", type="section", data={"text": "You can find cookies under \"Application\" in dev tools."}, required=False) EMPTY_FORM = Form( id="empty_auth", features=__FEATURES, questions=[__QUESTION], name="Auth form", description="An empty form to help you get a token.", ) class DiscoverableFormsList(Route): """ List all discoverable forms that should be shown on the homepage. """ name = "discoverable_forms_list" path = "/discoverable" @api.validate(resp=Response(HTTP_200=FormList), tags=["forms"]) async def get(self, request: Request) -> JSONResponse: