Browse Source

Add admin functionality

master
Chris Smith 5 years ago
parent
commit
e108295117
4 changed files with 78 additions and 2 deletions
  1. 1
    0
      README.md
  2. 59
    2
      main.py
  3. 18
    0
      templates/admin.html
  4. 0
    0
      templates/index.html

+ 1
- 0
README.md View File

17
 -------------------- | --------------------------------------------------------
17
 -------------------- | --------------------------------------------------------
18
  `LAS_BASE_URL`      | Base web-facing URL at which LaS is accessible, used to generate hook URLs
18
  `LAS_BASE_URL`      | Base web-facing URL at which LaS is accessible, used to generate hook URLs
19
  `LAS_SECRET`        | Random string used to generate secure hook URLs
19
  `LAS_SECRET`        | Random string used to generate secure hook URLs
20
+ `LAS_ADMIN_PASSWORD`| Password to require for web-based admin functionality
20
 
21
 
21
 ### URL format
22
 ### URL format
22
 
23
 

+ 59
- 2
main.py View File

1
-from flask import Flask, abort, request
1
+import os
2
+
3
+from collections import deque
4
+from flask import Flask, abort, request, Response, render_template
5
+from functools import wraps
2
 
6
 
3
 from lineandsinker.common import get_hook_key
7
 from lineandsinker.common import get_hook_key
4
 from lineandsinker.services import services
8
 from lineandsinker.services import services
10
 
14
 
11
 
15
 
12
 refresh_services()
16
 refresh_services()
17
+history = deque(maxlen=100)
13
 app = Flask(__name__)
18
 app = Flask(__name__)
14
 
19
 
15
 
20
 
16
 def handle_events(events):
21
 def handle_events(events):
17
     for event in events:
22
     for event in events:
23
+        history.append(event)
18
         if event["type"] == "git.push":
24
         if event["type"] == "git.push":
19
             services["jenkins"].build_job_by_scm_url(event["repo"]["urls"])
25
             services["jenkins"].build_job_by_scm_url(event["repo"]["urls"])
20
             if event["repo"]["public"]:
26
             if event["repo"]["public"]:
35
 
41
 
36
 @app.route("/")
42
 @app.route("/")
37
 def handle_index():
43
 def handle_index():
38
-    return app.send_static_file("index.html")
44
+    return render_template("index.html")
39
 
45
 
40
 
46
 
41
 @app.route("/hooks/<service>/<path:identifier>/<hash>", methods=["GET", "POST"])
47
 @app.route("/hooks/<service>/<path:identifier>/<hash>", methods=["GET", "POST"])
44
 
50
 
45
     expected_hash = get_hook_key(service, identifier)
51
     expected_hash = get_hook_key(service, identifier)
46
     if hash != expected_hash:
52
     if hash != expected_hash:
53
+        handle_events(
54
+            [
55
+                {
56
+                    "type": "lineandsinker.badhash",
57
+                    "service": service,
58
+                    "hash": hash,
59
+                    "body": request.get_data(as_text=True),
60
+                }
61
+            ]
62
+        )
47
         app.logger.info(f"Hash not valid. Expected: {expected_hash}, got {hash}")
63
         app.logger.info(f"Hash not valid. Expected: {expected_hash}, got {hash}")
48
         abort(403)
64
         abort(403)
49
 
65
 
50
     if service not in services:
66
     if service not in services:
67
+        handle_events(
68
+            [
69
+                {
70
+                    "type": "lineandsinker.badservice",
71
+                    "service": service,
72
+                    "known_services": list(services.keys()),
73
+                    "body": request.get_data(as_text=True),
74
+                }
75
+            ]
76
+        )
51
         app.logger.info(f"Unknown service {service}, known: {services.keys()}")
77
         app.logger.info(f"Unknown service {service}, known: {services.keys()}")
52
         abort(404)
78
         abort(404)
53
 
79
 
56
     return "", 200
82
     return "", 200
57
 
83
 
58
 
84
 
85
+if "LAS_ADMIN_PASSWORD" in os.environ:
86
+
87
+    def requires_auth(f):
88
+        @wraps(f)
89
+        def decorated(*args, **kwargs):
90
+            auth = request.authorization
91
+            if (
92
+                not auth
93
+                or auth.username != "admin"
94
+                or auth.password != os.environ["LAS_ADMIN_PASSWORD"]
95
+            ):
96
+                return Response(
97
+                    "Restricted resource",
98
+                    401,
99
+                    {"WWW-Authenticate": 'Basic realm="Login Required"'},
100
+                )
101
+            return f(*args, **kwargs)
102
+
103
+        return decorated
104
+
105
+    @app.route("/admin")
106
+    @requires_auth
107
+    def handle_admin():
108
+        return render_template("admin.html", events=history)
109
+
110
+    @app.route("/admin/hash", methods=["POST"])
111
+    @requires_auth
112
+    def handle_admin_hash():
113
+        return get_hook_key(request.form["service"], request.form["identifier"]), 200
114
+
115
+
59
 if __name__ == "__main__":
116
 if __name__ == "__main__":
60
     app.run("0.0.0.0")
117
     app.run("0.0.0.0")

+ 18
- 0
templates/admin.html View File

1
+<!DOCTYPE html>
2
+<html>
3
+<head>
4
+    <title>Line and Sinker Administration</title>
5
+</head>
6
+<body>
7
+    <h1>Determine hash</h1>
8
+    <form action="/admin/hash" method="post">
9
+        <label>Service: <input type="text" name="service"></label>
10
+        <label>Identifier: <input type="text" name="identifier"></label>
11
+        <input type="submit" value="Calculate">
12
+    </form>
13
+    <h1>Recent events</h1>
14
+    {% for item in events|reverse %}
15
+        <pre>{{ item|tojson(indent=4) }}</pre>
16
+    {% endfor %}
17
+</body>
18
+</html>

static/index.html → templates/index.html View File


Loading…
Cancel
Save