forked from arti/doors
		
	Add more web
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -64,3 +64,4 @@ target/ | |||||||
| # Other | # Other | ||||||
| venv/ | venv/ | ||||||
| *.sqlite | *.sqlite | ||||||
|  | ad.json | ||||||
|   | |||||||
| @@ -1,14 +1,53 @@ | |||||||
| import datetime | import datetime | ||||||
| import sqlite3 | import sqlite3 | ||||||
|  | import inspect | ||||||
|  | import json | ||||||
|  | from bottle import HTTPError | ||||||
|  |  | ||||||
| SCHEMA_VERSION = 1 | SCHEMA_VERSION = 1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SQLitePlugin: | ||||||
|  |  | ||||||
|  |     name = "sqlite" | ||||||
|  |     api = 2 | ||||||
|  |  | ||||||
|  |     def __init__(self, dbfile): | ||||||
|  |         self.dbfile = dbfile | ||||||
|  |  | ||||||
|  |     def apply(self, callback, context): | ||||||
|  |         # Test if the original callback accepts a 'db' keyword. | ||||||
|  |         # Ignore it if it does not need a database handle. | ||||||
|  |         args = inspect.signature(context.callback).parameters | ||||||
|  |         if "db" not in args: | ||||||
|  |             return callback | ||||||
|  |  | ||||||
|  |         def wrapper(*args, **kwargs): | ||||||
|  |             db = DB(self.dbfile) | ||||||
|  |             # Add the connection handle as a keyword argument. | ||||||
|  |             kwargs["db"] = db | ||||||
|  |             try: | ||||||
|  |                 rv = callback(*args, **kwargs) | ||||||
|  |             except sqlite3.IntegrityError as e: | ||||||
|  |                 db.db.rollback() | ||||||
|  |                 raise HTTPError(500, "Database Error", e) | ||||||
|  |             finally: | ||||||
|  |                 db.close() | ||||||
|  |             return rv | ||||||
|  |  | ||||||
|  |         # Replace the route callback with the wrapped one. | ||||||
|  |         return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
| class DB: | class DB: | ||||||
|  |  | ||||||
|     def __init__(self, dbfile=":memory:"): |     def __init__(self, dbfile=":memory:"): | ||||||
|         self.dbfile = dbfile |         self.dbfile = dbfile | ||||||
|         self.db = sqlite3.connect(self.dbfile) |         self.db = sqlite3.connect(self.dbfile) | ||||||
|  |         self.db.row_factory = sqlite3.Row | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         self.db.close() | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def create_db(dbfile): |     def create_db(dbfile): | ||||||
| @@ -21,7 +60,7 @@ class DB: | |||||||
|             create table users ( |             create table users ( | ||||||
|                 id integer primary key, |                 id integer primary key, | ||||||
|                 user text, |                 user text, | ||||||
|                 real_name text, |                 full_name text, | ||||||
|                 email text, |                 email text, | ||||||
|                 disabled integer, |                 disabled integer, | ||||||
|                 admin integer |                 admin integer | ||||||
| @@ -32,6 +71,7 @@ class DB: | |||||||
|                 card_uid blob, |                 card_uid blob, | ||||||
|                 name text, |                 name text, | ||||||
|                 created text, |                 created text, | ||||||
|  |                 disabled integer, | ||||||
|  |  | ||||||
|                 foreign key (user_id) |                 foreign key (user_id) | ||||||
|                     references users (id) |                     references users (id) | ||||||
| @@ -69,25 +109,69 @@ class DB: | |||||||
|         ) |         ) | ||||||
|         db.commit() |         db.commit() | ||||||
|  |  | ||||||
|     def list_users(*, page=0, count=20, query=None): |     def add_user(self, user, full_name=None, email=None, disabled=False, admin=False): | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def add_user(self, user, real_name=None, email=None, disabled=False, admin=False): |  | ||||||
|         self.add_users([( |         self.add_users([( | ||||||
|             user, real_name, email, disabled, admin |             user, full_name, email, int(disabled), int(admin) | ||||||
|         )]) |         )]) | ||||||
|  |  | ||||||
|     def add_users(self, users): |     def add_users(self, users): | ||||||
|         self.db.executemany(""" |         self.db.executemany(""" | ||||||
|         insert into users(user, real_name, email, disabled, admin) |         insert into users(user, full_name, email, disabled, admin) | ||||||
|         values(?, ?, ?, ?, ?) |         values(?, ?, ?, ?, ?) | ||||||
|         """, users) |         """, users) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|  |  | ||||||
|  |     def list_users(self): | ||||||
|  |         cur = self.db.execute( | ||||||
|  |             "select id, user, full_name, email from users" | ||||||
|  |         ) | ||||||
|  |         return cur.fetchall() | ||||||
|  |  | ||||||
|  |     def get_user(self, user_id): | ||||||
|  |         cur = self.db.execute( | ||||||
|  |             "select * from users where id = ?", | ||||||
|  |             (user_id, ) | ||||||
|  |         ) | ||||||
|  |         return cur.fetchone() | ||||||
|  |  | ||||||
|  |     def get_user_by_name(self, user_name): | ||||||
|  |         cur = self.db.execute( | ||||||
|  |             "select * from users where user = ?", | ||||||
|  |             (user_name, ) | ||||||
|  |         ) | ||||||
|  |         return cur.fetchone() | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def import_ad(json_file): | ||||||
|  |         with open(json_file) as fp: | ||||||
|  |             json_data = json.load(fp) | ||||||
|  |         for user, fields in json_data.items(): | ||||||
|  |             if fields.get("considered_active", False) and \ | ||||||
|  |                     fields.get("groups", {}).get("floor_access", False): | ||||||
|  |                 yield ( | ||||||
|  |                     user, | ||||||
|  |                     fields.get("full_name"), | ||||||
|  |                     fields.get("personal_mail"), | ||||||
|  |                     0, | ||||||
|  |                     int(fields.get("groups",{}).get("onboarding", False)) | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     #DB.create_db("kdoorweb.sqlite") |     dbfile = "../kdoorweb.sqlite" | ||||||
|     db = DB("kdoorweb.sqlite") |     import os, sys | ||||||
|     db.add_user("juku") |     from pprint import pprint | ||||||
|  |     try: | ||||||
|  |         os.unlink(dbfile) | ||||||
|  |     except FileNotFoundError: | ||||||
|  |         pass | ||||||
|  |     DB.create_db(dbfile) | ||||||
|  |     db = DB(dbfile) | ||||||
|  |     db.add_users(db.import_ad("../ad.json")) | ||||||
|  |     users = db.list_users() | ||||||
|  |     for user in users: | ||||||
|  |         print(dict(user)) | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | % from bottle import request | ||||||
| <!doctype html> | <!doctype html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
| @@ -10,14 +11,21 @@ | |||||||
|   <header> |   <header> | ||||||
|     <h1>K-Space Door system</h1> |     <h1>K-Space Door system</h1> | ||||||
|     <nav> |     <nav> | ||||||
|       <a href="/list">Users List</a> |       % if request.get("bottle.request.ext.current_user"): | ||||||
|       <a href="/log">Door Log</a> |         <a href="/list">Users</a> | ||||||
|       <a href="/logout">Logout</a> |         % if request.current_user["admin"]: | ||||||
|  |           <a href="/doors">Doors</a> | ||||||
|  |           <a href="/log">Log</a> | ||||||
|  |         % end | ||||||
|  |         <a href="/logout">Logout</a> | ||||||
|  |       % end | ||||||
|     </nav> |     </nav> | ||||||
|   </header> |   </header> | ||||||
|   <hr> |   <hr> | ||||||
|  |  | ||||||
|   {{!base}} |   {{!base}} | ||||||
|  |  | ||||||
|  | <hr> | ||||||
|  |  | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								kdoorweb/kdoorweb/views/doors.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								kdoorweb/kdoorweb/views/doors.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | % rebase('base.html') | ||||||
|  |  | ||||||
|  | <h3>Doors List</h3> | ||||||
|  |  | ||||||
|  | <form method="post"> | ||||||
|  |   <input name="action" value="Lock All" type="submit"> | ||||||
|  |   <input name="action" value="Unlock All" type="submit"> | ||||||
|  | </form> | ||||||
|  |  | ||||||
|  | <table class="double"> | ||||||
|  | <thead> | ||||||
|  |   <tr> | ||||||
|  |     <th>Name</th> | ||||||
|  |     <th>State</th> | ||||||
|  |     <th style="width:10em;">Action</th> | ||||||
|  |   </tr> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  |   % for door in doors: | ||||||
|  |     <tr> | ||||||
|  |       <td>{{door["name"]}}</td> | ||||||
|  |       <td>{{door["state"]}}</td> | ||||||
|  |       <td> | ||||||
|  |         <form method="post"> | ||||||
|  |           <input name="door_id" value="door['id']" type="hidden"> | ||||||
|  |           <input name="action" value="Lock" type="submit"> | ||||||
|  |           <input name="action" value="Unlock" type="submit"> | ||||||
|  |         </form> | ||||||
|  |       </td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <td colspan="3">{{door["note"]}}</td> | ||||||
|  |     </tr> | ||||||
|  |   % end | ||||||
|  | </tbody> | ||||||
|  | </table> | ||||||
							
								
								
									
										58
									
								
								kdoorweb/kdoorweb/views/info.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								kdoorweb/kdoorweb/views/info.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | % rebase('base.html') | ||||||
|  |  | ||||||
|  | <h3>Info {{full_name}}</h3> | ||||||
|  |  | ||||||
|  | <dl style="float:right;"> | ||||||
|  |   <dt>Disabled</dt> | ||||||
|  |   <dd>{{disabled}}</dd> | ||||||
|  |   <dt><abbr title="User is in Onboarding AD group">Admin</abbr></dt> | ||||||
|  |   <dd>{{admin}}</dd> | ||||||
|  | </dl> | ||||||
|  |  | ||||||
|  | <dl> | ||||||
|  |   <dt>User</dt> | ||||||
|  |   <dd>{{user}}</dd> | ||||||
|  |   <dt>Full Name</dt> | ||||||
|  |   <dd>{{full_name}}</dd> | ||||||
|  |   <dt>Email</dt> | ||||||
|  |   <dd>{{email}}</dd> | ||||||
|  | </dl> | ||||||
|  |  | ||||||
|  | <h3>Keycards</h3> | ||||||
|  |  | ||||||
|  | <table class="even"> | ||||||
|  | <thead> | ||||||
|  |   <tr> | ||||||
|  |     <th>Name</th> | ||||||
|  |     <th>Created</th> | ||||||
|  |     <th>Disabled</th> | ||||||
|  |     <th>Action</th> | ||||||
|  |   </tr> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  |   % for keycard in keycards: | ||||||
|  |     <tr> | ||||||
|  |       <td>{{keycard["name"]}}</td> | ||||||
|  |       <td>{{keycard["created"]}}</td> | ||||||
|  |       <td>{{keycard["disabled"]}}</td> | ||||||
|  |       <td> | ||||||
|  |         <form method="post"> | ||||||
|  |           <input name="keycard_id" value="keycard['id']" type="hidden"> | ||||||
|  |           <input name="action" value="disable" type="hidden"> | ||||||
|  |           <input value="Disable" type="submit"> | ||||||
|  |         </form> | ||||||
|  |       </td> | ||||||
|  |     </tr> | ||||||
|  |   % end | ||||||
|  | </tbody> | ||||||
|  | </table> | ||||||
|  |  | ||||||
|  | <h4>Add new keycard</h4> | ||||||
|  | <form method="post" class="inline"> | ||||||
|  |   <label for="name">Name: </label> | ||||||
|  |   <input id="name" name="name" type="text"> | ||||||
|  |   <label for="uid">UID: </label> | ||||||
|  |   <input id="uid" name="uid" type="text"> | ||||||
|  |   <input name="action" value="create" type="hidden"> | ||||||
|  |   <input value="Add" type="submit"> | ||||||
|  | </form> | ||||||
| @@ -6,15 +6,17 @@ | |||||||
| <thead> | <thead> | ||||||
|   <tr> |   <tr> | ||||||
|     <th>Name</th> |     <th>Name</th> | ||||||
|     <th>nr of cards</th> |     <th style="width:4em;">Cards</th> | ||||||
|     <th>Last access</th> |     <th style="width:10em;">Last access</th> | ||||||
|   </tr> |   </tr> | ||||||
| </thead> | </thead> | ||||||
| <tbody> | <tbody> | ||||||
|  |   % for user in users: | ||||||
|     <tr> |     <tr> | ||||||
|       <td><a href="/info/1">Arti Zirk</a></td> |       <td><a href="/info/{{user['id']}}">{{user["full_name"]}}</a></td> | ||||||
|       <td>2</td> |       <td>-</td> | ||||||
|       <td>-</td> |       <td>-</td> | ||||||
|     </tr> |     </tr> | ||||||
|  |   % end | ||||||
| </tbody> | </tbody> | ||||||
| </table> | </table> | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								kdoorweb/kdoorweb/views/log.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								kdoorweb/kdoorweb/views/log.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | % rebase('base.html') | ||||||
|  |  | ||||||
|  | <h3>Access log</h3> | ||||||
|  |  | ||||||
|  | <table class="even"> | ||||||
|  | <thead> | ||||||
|  |   <tr> | ||||||
|  |     <th>Time</th> | ||||||
|  |     <th>Door</th> | ||||||
|  |     <th>User</th> | ||||||
|  |   </tr> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  |   % for event in events: | ||||||
|  |     <tr> | ||||||
|  |       <td>{{event["timestamp"]}}</td> | ||||||
|  |       <td>{{event["door"]}}</td> | ||||||
|  |       <td><a href="/info/{{event['user_id']}}">{{event["user_name"]}}</a></td> | ||||||
|  |     </tr> | ||||||
|  |   % end | ||||||
|  | </tbody> | ||||||
|  | </table> | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| % rebase('base.html') | % rebase('base.html') | ||||||
|  |  | ||||||
|  | <h3>Login:</h3> | ||||||
|  |  | ||||||
| % if error: | % if error: | ||||||
|   <p>Error: {{ error }}</p> |   <p>Error: {{ error }}</p> | ||||||
| % end | % end | ||||||
|   | |||||||
| @@ -2,22 +2,45 @@ import os | |||||||
| import secrets | import secrets | ||||||
|  |  | ||||||
| from bottle import Bottle, view, TEMPLATE_PATH, static_file, \ | from bottle import Bottle, view, TEMPLATE_PATH, static_file, \ | ||||||
|     request, redirect, response |     request, redirect, response, HTTPError | ||||||
|  |  | ||||||
|  | from .db import SQLitePlugin | ||||||
|  |  | ||||||
| application = app = Bottle() | application = app = Bottle() | ||||||
|  |  | ||||||
| # TODO: Could be replaced with importlib.resources | # TODO: Could be replaced with importlib.resources | ||||||
| TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views")) | TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views")) | ||||||
| STATIC_PATH = os.path.join(os.path.dirname(__file__), "static") | STATIC_PATH = os.path.join(os.path.dirname(__file__), "static") | ||||||
|  | SQLITE_PATH = os.path.join(os.path.dirname(__file__), "..", "kdoorweb.sqlite") | ||||||
|  |  | ||||||
| COOKIE_KEY = secrets.token_bytes(32) | COOKIE_KEY = b"1234"#secrets.token_bytes(32) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def current_user(db): | ||||||
|  |     user_id = request.get_cookie("uid", secret=COOKIE_KEY) | ||||||
|  |     if not user_id: | ||||||
|  |         return | ||||||
|  |     return db.get_user(user_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def login_user(uid): | ||||||
|  |     response.set_cookie("uid", uid, secret=COOKIE_KEY) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def logout_user(): | ||||||
|  |     response.set_cookie("uid", "", secret=COOKIE_KEY) | ||||||
|  |  | ||||||
|  |  | ||||||
| def check_auth(callback): | def check_auth(callback): | ||||||
|     def wrapper(*args, **kwargs): |     def wrapper(*args, **kwargs): | ||||||
|         user = request.get_cookie("user", key=COOKIE_KEY) |         if "db" not in kwargs: | ||||||
|  |             request.current_user = None | ||||||
|  |             return callback(*args, **kwargs) | ||||||
|  |         user = current_user(kwargs["db"]) | ||||||
|  |         request.current_user = user | ||||||
|         if user: |         if user: | ||||||
|             print(f"logged in as {user}") |             print(f"logged in as {user['user']}") | ||||||
|  |             print(request.current_user) | ||||||
|             return callback(*args, **kwargs) |             return callback(*args, **kwargs) | ||||||
|         else: |         else: | ||||||
|             print("not logged in") |             print("not logged in") | ||||||
| @@ -26,6 +49,7 @@ def check_auth(callback): | |||||||
|     return wrapper |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | app.install(SQLitePlugin(SQLITE_PATH)) | ||||||
| app.install(check_auth) | app.install(check_auth) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -35,7 +59,14 @@ def callback(path): | |||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/", skip=[check_auth]) | @app.route("/", skip=[check_auth]) | ||||||
| def index(): | def index(db): | ||||||
|  |     user = current_user(db) | ||||||
|  |     if user: | ||||||
|  |         if user["admin"]: | ||||||
|  |             redirect("/list") | ||||||
|  |         else: | ||||||
|  |             redirect(f"/info/{user['id']}") | ||||||
|  |  | ||||||
|     redirect("/login") |     redirect("/login") | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -49,31 +80,71 @@ def login(): | |||||||
|  |  | ||||||
|  |  | ||||||
| @app.post('/login', skip=[check_auth]) | @app.post('/login', skip=[check_auth]) | ||||||
| def do_login(): | def do_login(db): | ||||||
|     print(dict(request.forms)) |     user_name = request.forms.get("user") | ||||||
|     user = request.forms.get("user") |     user = db.get_user_by_name(user_name) | ||||||
|     print(f"user {user}") |  | ||||||
|     if user: |     if user: | ||||||
|         response.set_cookie("user", user, key=COOKIE_KEY) |         print(f"user {dict(user)}") | ||||||
|         redirect("/list") |         login_user(user["id"]) | ||||||
|  |         redirect("/") | ||||||
|     else: |     else: | ||||||
|         response.set_cookie("error", "No user") |         response.set_cookie("error", "Login Failed") | ||||||
|         redirect("/login") |         redirect("/login") | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/logout") | @app.route("/logout") | ||||||
| def logout(): | def logout(): | ||||||
|     response.set_cookie("user", "", key=COOKIE_KEY) |     logout_user() | ||||||
|     redirect("/login") |     redirect("/login") | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/list") | @app.route("/list") | ||||||
| @view("list.html") | @view("list.html") | ||||||
| def list(): | def user_list(db): | ||||||
|     return {} |     user = request.current_user | ||||||
|  |     if user and not user["admin"]: | ||||||
|  |         users = [user] | ||||||
|  |     else: | ||||||
|  |         users = db.list_users() | ||||||
|  |     return {"users": users} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/info") | ||||||
|  | def user_info(db): | ||||||
|  |     user = request.current_user | ||||||
|  |     redirect(f"/info/{user['id']}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/info/<user_id>") | ||||||
|  | @view("info.html") | ||||||
|  | def info(db, user_id): | ||||||
|  |     user_id = int(user_id) | ||||||
|  |     c_user = request.current_user | ||||||
|  |     if not c_user["admin"] and c_user["id"] != user_id: | ||||||
|  |         raise HTTPError(403, "Logged in user is not admin") | ||||||
|  |     user = db.get_user(user_id) | ||||||
|  |     if not user: | ||||||
|  |         raise HTTPError(404, "User does not exist") | ||||||
|  |     return {**user, "keycards": []} | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/log") | @app.route("/log") | ||||||
| @view("log") | @view("log.html") | ||||||
| def log(): | def log(db): | ||||||
|     return {} |     return { | ||||||
|  |         "events": | ||||||
|  |                 [ | ||||||
|  |                     { | ||||||
|  |                         "timestamp": 0, | ||||||
|  |                         "door": "Back Door", | ||||||
|  |                         "user_id":1, | ||||||
|  |                         "user_name": "Arti Zirk" | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/doors") | ||||||
|  | @view("doors.html") | ||||||
|  | def doors(db): | ||||||
|  |     return {"doors":[]} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user