summaryrefslogtreecommitdiff
path: root/guard/application.py
blob: 4782bac7c4416a8040f3d6df1ae281cce3c450f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from .model import Access
from . import templates

import logging
from fnmatch import fnmatchcase
from urllib.request import urlopen
from urllib.parse import urlencode

from pyinflux.parser import LineParser
from flask import request, redirect, session, url_for, make_response, Response
from werkzeug.exceptions import InternalServerError, Forbidden, Unauthorized, PreconditionFailed


def make_app(app, config):
    cookiename = config['cookiename']

    @app.route("/", methods=["POST", "GET"])
    def index():
        if "valid" not in session:
            return redirect(url_for("login"))

        if request.method == "POST":
            Access.create(token=request.form["token"],
                          pattern=request.form["pattern"],
                          comment=request.form["comment"])
        return templates.index(config['port'], cookiename)

    @app.route("/delete", methods=["POST"])
    def delete():
        if "valid" not in session:
            return redirect(url_for("login"))
        Access.delete().where(Access.id == int(request.form["id"])).execute()
        return redirect(url_for("index"))

    @app.route("/logout")
    def logout():
        del session["valid"]
        return redirect(url_for("login"))

    @app.route("/login", methods=["POST", "GET"])
    def login():
        if request.method == "POST":
            if "secret" in request.form and \
                            request.form["secret"] == config["adminsecret"]:
                logging.info("Login successful")
                session["valid"] = True
                return redirect(url_for("index"))
            else:
                logging.info("Login failed")
                return redirect(url_for("login"))
        else:
            return templates.login()

    @app.route("/write/<token>", methods=["POST"])
    @app.route("/write", methods=["POST"])
    def write(token=None):
        # Find token cookie in request
        if not token:
            token = request.cookies.get(cookiename)

        valid_access_patterns = tuple(map(lambda x: x[0],
                                        Access.select(Access.pattern).where(Access.token == token).tuples()))
        if not valid_access_patterns:
            return make_response("Token {} is not configured\n".format(token), Forbidden.code)

        # Read request
        database = request.args.get("db")
        if not database:
            return make_response("No database name given in parameter 'db'\n", PreconditionFailed.code)

        # Read data
        data = request.get_data(as_text=False)
        data_text = data.decode(request.charset, request.encoding_errors)

        # validate access
        for line in data_text.split("\n"):
            identifier = database + '.' + LineParser.parse_identifier(line)
            if not any(map(lambda pattern: fnmatchcase(identifier, pattern), valid_access_patterns)):
                logging.info("Reject write to %s", identifier)
                return make_response("Invalid path: {}\n".format(identifier), Unauthorized.code)


        # checks passed, forward the request
        params = {'db': database}
        if request.args.get("rp"):
            params["rp"] = request.args.get("rp")
        if request.args.get("precision"):
            params["precision"] = request.args.get("precision")
        if request.args.get("consistency"):
            params["consistency"] = request.args.get("consistency")

        if config.get("username"):
            params["u"] = config["username"]
        if config.get("password"):
            params["p"] = config["password"]

        url = config['url'] + "?" + urlencode(params)
        try:
            with urlopen(url, data) as resp:
                if logging.root.isEnabledFor(logging.INFO):
                    logging.info("Forwarded request to database '%s' for '%s' with '%s': %s",
                                 database, request.remote_addr, token, data_text)
                return Response(resp.fp, status=resp.getcode())
        except:
            logging.exception("Request failed for:\n%s", data)
            return make_response("Failed", InternalServerError.code)