Example #1
0
def register_routes():
    return (
        (
            r"^/til/til/(?P<topic>[^_]+)_(?P<slug>[^\.]+)\.md$",
            lambda request: Response.redirect(
                "/{topic}/{slug}".format(**request.url_vars), status=301),
        ),
        ("^/til/feed.atom$",
         lambda: Response.redirect("/tils/feed.atom", status=301)),
        (
            "^/til$",
            lambda request: Response.redirect(
                "/tils" + (("?" + request.query_string)
                           if request.query_string else ""),
                status=301,
            ),
        ),
        (
            "^/til/search$",
            lambda request: Response.redirect(
                "/tils/search" + (("?" + request.query_string)
                                  if request.query_string else ""),
                status=301,
            ),
        ),
    )
Example #2
0
async def _tiles_stack(datasette, request, tms):
    priority_order = await tiles_stack_database_order(datasette)
    # Try each database in turn
    for database in priority_order:
        tile = await load_tile(database, request, tms=tms)
        if tile is not None:
            return Response(body=tile, content_type="image/png")
    return Response(body=PNG_404, content_type="image/png", status=404)
Example #3
0
async def _tile(request, datasette, tms):
    db_name = request.url_vars["db_name"]
    mbtiles_databases = await detect_mtiles_databases(datasette)
    if db_name not in mbtiles_databases:
        raise NotFound("Not a valid mbtiles database")
    db = datasette.get_database(db_name)
    tile = await load_tile(db, request, tms)
    if tile is None:
        return Response(body=PNG_404, content_type="image/png", status=404)
    return Response(body=tile, content_type="image/png")
Example #4
0
async def manage_db_group(scope, receive, datasette, request):
    db_name = unquote_plus(request.url_vars["database"])
    if not await datasette.permission_allowed(
        request.actor, "live-permissions-edit", db_name, default=False
    ):
        raise Forbidden("Permission denied")

    db = get_db(datasette)

    group_id = None
    results = db["groups"].rows_where("name=?", [f"DB Access: {db_name}"])
    for row in results:
        group_id = row["id"]
        break

    assert db_name in datasette.databases, "Non-existant database!"

    if not group_id and db_name not in BLOCKED_DB_ACTIONS:
        db["groups"].insert({
            "name": f"DB Access: {db_name}",
        }, pk="id", replace=True)
        return await manage_db_group(scope, receive, datasette, request)

    if request.method in ["POST", "DELETE"]:
        formdata = await request.post_vars()
        user_id = formdata["user_id"]

        if request.method == "POST":
            db["group_membership"].insert({
                "group_id": group_id,
                "user_id": user_id,
            }, replace=True)
        elif request.method == "DELETE":
            db["group_membership"].delete((group_id, user_id))
            return Response.text('', status=204)
        else:
            raise NotImplementedError(f"Bad method: {request.method}")

    perms_query = """
        select distinct user_id as id, lookup, value, description
        from group_membership join users
        on group_membership.user_id = users.id
        where group_membership.group_id=?
    """
    users = db.execute(perms_query, (group_id,))
    return Response.html(
        await datasette.render_template(
            "database_management.html", {
                "database": db_name,
                "users": users,
            }, request=request
        )
    )
Example #5
0
 async def get(self, request):
     token = request.args.get("token") or ""
     if not self.ds._root_token:
         return Response("Root token has already been used", status=403)
     if secrets.compare_digest(token, self.ds._root_token):
         self.ds._root_token = None
         response = Response.redirect("/")
         response.set_cookie("ds_actor",
                             self.ds.sign({"a": {
                                 "id": "root"
                             }}, "actor"))
         return response
     else:
         return Response("Invalid token", status=403)
Example #6
0
async def live_config(scope, receive, datasette, request):
    submit_url = request.path
    database_name = unquote_plus(
        request.url_vars.get("database_name", "global"))
    meta_in_db = True if request.args.get("meta_in_db") else False
    if meta_in_db:
        submit_url += '?meta_in_db=true'
    table_name = "global"
    perm_args = ()
    if database_name:
        perm_args = (database_name, )
    if not await datasette.permission_allowed(
            request.actor, "live-config", *perm_args, default=False):
        raise Forbidden("Permission denied for live-config")

    if request.method != "POST":
        # TODO: Decide if we use this or pull saved config
        metadata = datasette.metadata()
        if database_name and database_name != "global":
            metadata = metadata["databases"].get(database_name, {})
        return Response.html(await datasette.render_template(
            "config_editor.html", {
                "database_name": database_name,
                "configJSON": json.dumps(metadata),
                "submit_url": submit_url,
            },
            request=request))

    formdata = await request.post_vars()
    if meta_in_db and database_name in datasette.databases:
        db_meta = json.loads(formdata["config"])
        update_db_metadata(datasette.databases[database_name], db_meta)
    else:
        update_live_config_db(datasette, database_name, table_name,
                              formdata["config"])

    metadata = datasette.metadata()
    if database_name != "global":
        metadata = metadata["databases"][database_name]
    return Response.html(await datasette.render_template(
        "config_editor.html", {
            "database_name": database_name,
            "message": "Configuration updated successfully!",
            "status": "success",
            "configJSON": json.dumps(metadata),
            "submit_url": submit_url,
        },
        request=request))
Example #7
0
    async def render(self, templates, request, context):
        template = self.ds.jinja_env.select_template(templates)
        select_templates = [
            "{}{}".format("*" if template_name == template.name else "",
                          template_name) for template_name in templates
        ]
        body_scripts = []
        # pylint: disable=no-member
        for script in pm.hook.extra_body_script(
                template=template.name,
                database=context.get("database"),
                table=context.get("table"),
                view_name=self.name,
                datasette=self.ds,
        ):
            body_scripts.append(jinja2.Markup(script))

        extra_template_vars = {}
        # pylint: disable=no-member
        for extra_vars in pm.hook.extra_template_vars(
                template=template.name,
                database=context.get("database"),
                table=context.get("table"),
                view_name=self.name,
                request=request,
                datasette=self.ds,
        ):
            if callable(extra_vars):
                extra_vars = extra_vars()
            if asyncio.iscoroutine(extra_vars):
                extra_vars = await extra_vars
            assert isinstance(extra_vars,
                              dict), "extra_vars is of type {}".format(
                                  type(extra_vars))
            extra_template_vars.update(extra_vars)

        return Response.html(await template.render_async({
            **context,
            **{
                "app_css_hash":
                self.ds.app_css_hash(),
                "select_templates":
                select_templates,
                "zip":
                zip,
                "body_scripts":
                body_scripts,
                "extra_css_urls":
                self._asset_urls("extra_css_urls", template, context),
                "extra_js_urls":
                self._asset_urls("extra_js_urls", template, context),
                "format_bytes":
                format_bytes,
                "database_url":
                self.database_url,
                "database_color":
                self.database_color,
            },
            **extra_template_vars,
        }))
Example #8
0
 async def render(self, templates, request, context=None):
     context = context or {}
     template = self.ds.jinja_env.select_template(templates)
     template_context = {
         **context,
         **{
             "database_url":
             self.database_url,
             "csrftoken":
             request.scope["csrftoken"],
             "database_color":
             self.database_color,
             "show_messages":
             lambda: self.ds._show_messages(request),
             "select_templates": [
                 "{}{}".format(
                     "*" if template_name == template.name else "", template_name) for template_name in templates
             ],
         },
     }
     return Response.html(await
                          self.ds.render_template(template,
                                                  template_context,
                                                  request=request,
                                                  view_name=self.name))
Example #9
0
 async def render(self, templates, request, context=None):
     context = context or {}
     template = self.ds.jinja_env.select_template(templates)
     template_context = {
         **context,
         **{
             "database_color":
             self.database_color,
             "select_templates": [
                 f"{'*' if template_name == template.name else ''}{template_name}" for template_name in templates
             ],
         },
     }
     headers = {}
     if self.has_json_alternate:
         alternate_url_json = self.ds.absolute_url(
             request,
             self.ds.urls.path(
                 path_with_format(request=request, format="json")),
         )
         template_context["alternate_url_json"] = alternate_url_json
         headers.update({
             "Link":
             '{}; rel="alternate"; type="application/json+datasette"'.
             format(alternate_url_json)
         })
     return Response.html(
         await self.ds.render_template(
             template,
             template_context,
             request=request,
             view_name=self.name,
         ),
         headers=headers,
     )
Example #10
0
async def tiles_stack_explorer(datasette):
    attribution = ""
    # Find min/max zoom by looking at the stack
    priority_order = await tiles_stack_database_order(datasette)
    min_zooms = []
    max_zooms = []
    attributions = []
    for db in priority_order:
        metadata = {
            row["name"]: row["value"]
            for row in (
                await db.execute("select name, value from metadata")).rows
        }
        if "minzoom" in metadata:
            min_zooms.append(int(metadata["minzoom"]))
        if "maxzoom" in metadata:
            max_zooms.append(int(metadata["maxzoom"]))
    # If all attributions are the same, use that - otherwise leave blank
    if len(set(attributions)) == 1:
        attribution = attributions[0]
    min_zoom = min(min_zooms)
    max_zoom = max(max_zooms)
    return Response.html(await datasette.render_template(
        "tiles_stack_explorer.html",
        {
            "default_latitude": 0,
            "default_longitude": 0,
            "default_zoom": min_zoom,
            "min_zoom": min_zoom,
            "max_zoom": max_zoom,
            "attribution": json.dumps(attribution),
        },
    ))
Example #11
0
 def login_as_root(datasette, request):
     # Mainly for the latest.datasette.io demo
     if request.method == "POST":
         response = Response.redirect("/")
         response.set_cookie("ds_actor",
                             datasette.sign({"a": {
                                 "id": "root"
                             }}, "actor"))
         return response
     return Response.html("""
         <form action="{}" method="POST">
             <p>
                 <input type="hidden" name="csrftoken" value="{}">
                 <input type="submit" value="Sign in as root user"></p>
         </form>
     """.format(request.path, request.scope["csrftoken"]()))
Example #12
0
async def dashboard_chart(request, datasette):
    await check_permission_instance(request, datasette)

    config = datasette.plugin_config("datasette-dashboards") or {}
    slug = urllib.parse.unquote(request.url_vars["slug"])
    chart_slug = urllib.parse.unquote(request.url_vars["chart_slug"])

    try:
        dashboard = config[slug]
    except KeyError:
        raise NotFound(f"Dashboard not found: {slug}")

    try:
        chart = dashboard["charts"][chart_slug]
    except KeyError:
        raise NotFound(f"Chart does not exist: {chart_slug}")

    db = chart.get("db")
    if db:
        database = datasette.get_database(db)
        await check_permission_execute_sql(request, datasette, database)

    options_keys = get_dashboard_filters_keys(request, dashboard)
    query_string = generate_dashboard_filters_qs(request, options_keys)
    fill_chart_query_options(chart, options_keys)

    return Response.html(await datasette.render_template(
        "dashboard_chart.html",
        {
            "slug": slug,
            "query_string": query_string,
            "dashboard": dashboard,
            "chart": chart,
        },
    ))
Example #13
0
async def dashboard_list(request, datasette):
    await check_permission_instance(request, datasette)
    config = datasette.plugin_config("datasette-dashboards") or {}
    return Response.html(await datasette.render_template(
        "dashboard_list.html",
        {"dashboards": config},
    ))
Example #14
0
    async def get(self, request, as_format):
        await self.check_permission(request, "view-instance")
        if self.needs_request:
            data = self.data_callback(request)
        else:
            data = self.data_callback()
        if as_format:
            headers = {}
            if self.ds.cors:
                headers["Access-Control-Allow-Origin"] = "*"
            return Response(
                json.dumps(data),
                content_type="application/json; charset=utf-8",
                headers=headers,
            )

        else:
            return await self.render(
                ["show_json.html"],
                request=request,
                context={
                    "filename": self.filename,
                    "data_json": json.dumps(data, indent=4),
                },
            )
Example #15
0
    async def get(self, request):
        as_format = request.url_vars["format"]
        await self.ds.ensure_permissions(request.actor, ["view-instance"])
        if self.needs_request:
            data = self.data_callback(request)
        else:
            data = self.data_callback()
        if as_format:
            headers = {}
            if self.ds.cors:
                add_cors_headers(headers)
            return Response(
                json.dumps(data),
                content_type="application/json; charset=utf-8",
                headers=headers,
            )

        else:
            return await self.render(
                ["show_json.html"],
                request=request,
                context={
                    "filename": self.filename,
                    "data_json": json.dumps(data, indent=4),
                },
            )
Example #16
0
def register_routes():
    return (
        # Homepage
        (r"^/$", lambda: Response.redirect("/us/pillar-point")),
        # country/slug - US only for the moment
        (r"^/us/(?P<slug>[^/]+)$", place_page),
    )
Example #17
0
async def test_response_set_cookie():
    events = []

    async def send(event):
        events.append(event)

    response = Response.redirect("/foo")
    response.set_cookie("foo", "bar", max_age=10, httponly=True)
    await response.asgi_send(send)

    assert [
        {
            "type":
            "http.response.start",
            "status":
            302,
            "headers": [
                [b"Location", b"/foo"],
                [b"content-type", b"text/plain"],
                [
                    b"set-cookie",
                    b"foo=bar; HttpOnly; Max-Age=10; Path=/; SameSite=lax"
                ],
            ],
        },
        {
            "type": "http.response.body",
            "body": b""
        },
    ] == events
Example #18
0
 async def get(self, request):
     if not await self.ds.permission_allowed(request.actor, "permissions-debug"):
         return Response("Permission denied", status=403)
     return await self.render(
         ["permissions_debug.html"],
         request,
         {"permission_checks": reversed(self.ds._permission_checks)},
     )
Example #19
0
 async def render(self, templates, request, context):
     template = self.ds.jinja_env.select_template(templates)
     template_context = {
         **context,
         **{
             "database_url": self.database_url,
             "database_color": self.database_color,
         },
     }
     if (request and request.args.get("_context")
             and self.ds.config("template_debug")):
         return Response.html("<pre>{}</pre>".format(
             jinja2.escape(
                 json.dumps(template_context, default=repr, indent=4))))
     return Response.html(await self.ds.render_template(template,
                                                        template_context,
                                                        request=request))
async def paprika_recipe_link(request, datasette, rows):
    row = rows[0]

    return Response.html(await datasette.render_template(
        "recipe.html",
        dict(zip(row.keys(), tuple(row))),
        request=request,
    ))
Example #21
0
async def view_graphql_schema(request, datasette):
    database = request.url_vars.get("database")
    try:
        datasette.get_database(database)
    except KeyError:
        raise NotFound("Database does not exist")
    schema = await schema_for_database_via_cache(datasette, database=database)
    return Response.text(print_schema(schema))
Example #22
0
 async def get(self, request):
     if not request.actor:
         return Response.redirect("/")
     return await self.render(
         ["logout.html"],
         request,
         {"actor": request.actor},
     )
Example #23
0
async def reconcile(request, datasette):
    database = request.url_vars["db_name"]
    table = request.url_vars["db_table"]
    db = datasette.get_database(database)

    # get plugin configuration
    config = datasette.plugin_config(
        "datasette-reconcile", database=database, table=table
    )
    config = await check_config(config, db, table)

    # check user can at least view this table
    await check_permissions(
        request,
        [
            ("view-table", (database, table)),
            ("view-database", database),
            "view-instance",
        ],
        datasette,
    )

    # work out if we are looking for queries
    post_vars = await request.post_vars()
    queries = post_vars.get("queries", request.args.get("queries"))
    if queries:
        queries = json.loads(queries)
        return Response.json(
            {
                q[0]: {"result": q[1]}
                async for q in reconcile_queries(queries, config, db, table)
            },
            headers={
                "Access-Control-Allow-Origin": "*",
            },
        )

    # if we're not then just return the service specification
    return Response.json(
        service_manifest(config, database, table, datasette, request),
        headers={
            "Access-Control-Allow-Origin": "*",
        },
    )
Example #24
0
 def redirect(self, request, path, forward_querystring=True, remove_args=None):
     if request.query_string and "?" not in path and forward_querystring:
         path = "{}?{}".format(path, request.query_string)
     if remove_args:
         path = path_with_removed_args(request, remove_args, path=path)
     r = Response.redirect(path)
     r.headers["Link"] = "<{}>; rel=preload".format(path)
     if self.ds.cors:
         r.headers["Access-Control-Allow-Origin"] = "*"
     return r
Example #25
0
def handle_exception(datasette, request, exception):
    datasette._exception_hook_fired = (request, exception)
    if request.args.get("_custom_error"):
        return Response.text("_custom_error")
    elif request.args.get("_custom_error_async"):

        async def inner():
            return Response.text("_custom_error_async")

        return inner
async def paprika_recipe_route(request, datasette):
    r = await datasette.client.get(
        f'paprika/recipes/{request.url_vars["recipe_id"]}.json')
    row_data = r.json()

    return Response.html(await datasette.render_template(
        "recipe.html",
        dict(zip(row_data["columns"], row_data["rows"][0])),
        request=request,
    ))
Example #27
0
def robots_txt(datasette):
    config = datasette.plugin_config("datasette-block-robots") or {}
    literal = config.get("literal")
    disallow = []
    if literal:
        return Response.text(literal)
    disallow = config.get("disallow") or []
    if isinstance(disallow, str):
        disallow = [disallow]
    allow_only_index = config.get("allow_only_index")
    if allow_only_index:
        for database_name in datasette.databases:
            if database_name != "_internal":
                disallow.append(datasette.urls.database(database_name))
    if not disallow:
        disallow = ["/"]
    lines = ["User-agent: *"
             ] + ["Disallow: {}".format(item) for item in disallow]
    return Response.text("\n".join(lines))
Example #28
0
 def redirect(self, request, path, forward_querystring=True, remove_args=None):
     if request.query_string and "?" not in path and forward_querystring:
         path = f"{path}?{request.query_string}"
     if remove_args:
         path = path_with_removed_args(request, remove_args, path=path)
     r = Response.redirect(path)
     r.headers["Link"] = f"<{path}>; rel=preload"
     if self.ds.cors:
         add_cors_headers(r.headers)
     return r
Example #29
0
 async def get(self, request):
     token = request.args.get("token") or ""
     if not self.ds._root_token:
         return Response("Root token has already been used", status=403)
     if secrets.compare_digest(token, self.ds._root_token):
         self.ds._root_token = None
         cookie = SimpleCookie()
         cookie["ds_actor"] = self.ds.sign({"id": "root"}, "actor")
         cookie["ds_actor"]["path"] = "/"
         response = Response(
             body="",
             status=302,
             headers={
                 "Location": "/",
                 "set-cookie": cookie.output(header="").lstrip(),
             },
         )
         return response
     else:
         return Response("Invalid token", status=403)
async def schema_versions(datasette, request):
    return Response.html(
        await datasette.render_template(
            "show_json.html",
            {
                "filename": "schema-versions.json",
                "data_json": json.dumps(await _schema_versions(datasette), indent=4),
            },
            request=request,
        )
    )