Пример #1
0
    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())
Пример #2
0
    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))
Пример #3
0
    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())
Пример #4
0
    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)
Пример #5
0
    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)
Пример #6
0
    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)
Пример #7
0
    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: