From 76cc8e6883eeecffa860ae214f4caf5f549639f3 Mon Sep 17 00:00:00 2001 From: rasmus Date: Fri, 8 Aug 2025 03:34:06 +0300 Subject: [PATCH] move /cards to doorboy-proxy + refactor mongo format --- deployment.yaml | 4 --- inventory-app/api.py | 70 ++++++++-------------------------------- inventory-app/common.py | 31 ++++++------------ inventory-app/const.py | 2 -- inventory-app/doorboy.py | 65 +++++++++++++++---------------------- 5 files changed, 48 insertions(+), 124 deletions(-) diff --git a/deployment.yaml b/deployment.yaml index a08dbf2..40a0628 100644 --- a/deployment.yaml +++ b/deployment.yaml @@ -23,12 +23,8 @@ spec: env: - name: OIDC_USERS_NAMESPACE value: "default" - - name: SLACK_DOORLOG_CALLBACK - value: "changeme" - name: SLACK_VERIFICATION_TOKEN value: "changeme" - - name: INVENTORY_API_KEY - value: "sptWL6XFxl4b8" - name: PYTHONUNBUFFERED value: "1" # Google test key diff --git a/inventory-app/api.py b/inventory-app/api.py index 537b067..ff33127 100644 --- a/inventory-app/api.py +++ b/inventory-app/api.py @@ -1,57 +1,14 @@ -import re -import const -import time import threading +import time from datetime import datetime -from functools import wraps + +import const +from flask import Blueprint, g, request from pymongo import MongoClient -from flask import Blueprint, g, request, jsonify -from common import slack_post page_api = Blueprint("api", __name__) db = MongoClient(const.MONGO_URI).get_default_database() -def check_api_key(f): - @wraps(f) - def decorated_function(*args, **kwargs): - request_key = request.headers.get('Authorization', False) - if not request_key: - return "nope", 403 - found_key = re.search(r"Basic (.*)", request_key).group(1) - if not found_key or found_key != const.INVENTORY_API_KEY: - return "nope", 403 - return f(*args, **kwargs) - return decorated_function - -@page_api.route("/cards", methods=["POST"]) -@check_api_key -def get_group_cards(): - request_groups = request.json.get("groups", False) - if not request_groups: - return "must specify groups in parameter", 400 - print(f"found {len(g.users)} users for groups: {request_groups}") - keys = [] - for u in g.users: - for group in u.groups: - if group in request_groups: - keys.append(u.username) - break - print(f"{len(keys)} doorkeys") - flt = { - "token.uid_hash": {"$exists": True}, - "inventory.owner.username": {"$in": keys} - } - prj = { - "inventory.owner": True, - "token.uid_hash": True - } - found = [] - for obj in db.inventory.find(flt, prj): - found.append({"token": obj["token"]}) - fl = list(found) - print(f"{len(fl)} doorkey tokens") - return jsonify(fl) - @page_api.route("/api/slack/doorboy", methods=['POST']) def view_slack_doorboy(): begin_time = time.perf_counter() @@ -91,27 +48,26 @@ def view_slack_doorboy(): threading.Thread(target=handle_slack_door_event, args=(doors, approved, member, door, status, subject)).start() - return_message = "Opening %s for %s" % (door, subject) if approved else "Permission denied" end_time = time.perf_counter() print(f"view_slack_doorboy done in {end_time - begin_time:.4f} seconds") + + return_message = "Opening %s for %s" % (door, subject) if approved else "Permission denied" return return_message def handle_slack_door_event(doors, approved, member, door, status, subject): begin_time = time.perf_counter() for d in doors: db.eventlog.insert_one({ - "method": "slack", - "approved": approved, - "duration": 5, "component": "doorboy", - "type": "open-door", - "door": d, - "member_id": member.username, - "member": member.display_name, + "method": "slack", "timestamp": datetime.utcnow(), + "door": d, + "approved": approved, + "user": { + "id": member.username, + "name": member.display_name, + } }) - msg = "%s %s door access for %s via Slack bot" % (status, door, subject) - slack_post(msg, "doorboy") end_time = time.perf_counter() print(f"handle_slack_door_event done in {end_time - begin_time:.4f} seconds") diff --git a/inventory-app/common.py b/inventory-app/common.py index 7f36f5d..1e5cf69 100644 --- a/inventory-app/common.py +++ b/inventory-app/common.py @@ -1,17 +1,16 @@ -import os import collections.abc -from functools import wraps - -import requests -from bson.objectid import ObjectId -from flask import g, request -from flask_wtf import FlaskForm -from pymongo import MongoClient -from kubernetes import client, config -from typing import List +import os from dataclasses import dataclass, field +from functools import wraps +from typing import List import const +from bson.objectid import ObjectId +from common import read_user +from flask import g, request +from flask_wtf import FlaskForm +from kubernetes import client, config +from pymongo import MongoClient devenv = const.ENVIRONMENT_TYPE == "DEV" OIDC_USERS_NAMESPACE = os.getenv("OIDC_USERS_NAMESPACE") @@ -93,18 +92,6 @@ def flatten(d, parent_key='', sep='.'): items.append((new_key, v)) return dict(items) -def slack_post(msg, channel): - if devenv: - print(f"{channel}: {msg}") - return - - channels = { - "doorboy": const.SLACK_DOORLOG_CALLBACK, - } - url = channels.get(channel, const.SLACK_DOORLOG_CALLBACK) - - requests.post(url, json={"text": msg }) - def build_query(base_query, fields=[], sort_fields={}): top_usernames= ['k-space'] selectors = [] diff --git a/inventory-app/const.py b/inventory-app/const.py index 9b8fc7c..0fd32ba 100644 --- a/inventory-app/const.py +++ b/inventory-app/const.py @@ -18,6 +18,4 @@ BUCKET_NAME = os.environ["BUCKET_NAME"] INVENTORY_ASSETS_BASE_URL = os.environ["INVENTORY_ASSETS_BASE_URL"] MONGO_URI = os.environ["MONGO_URI"] SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"] # used to verify (deprecated) incoming requests from slack -SLACK_DOORLOG_CALLBACK = os.environ["SLACK_DOORLOG_CALLBACK"] # used for sending logs to private channel -INVENTORY_API_KEY = os.environ["INVENTORY_API_KEY"] # used by doorboy-proxy (@check_api_key) MACADDRESS_OUTLINK_BASEURL = os.environ["MACADDRESS_OUTLINK_BASEURL"] diff --git a/inventory-app/doorboy.py b/inventory-app/doorboy.py index 066f305..bad2ae8 100644 --- a/inventory-app/doorboy.py +++ b/inventory-app/doorboy.py @@ -1,17 +1,17 @@ from datetime import datetime, timedelta -from dateutil.parser import parse, ParserError -from bson.objectid import ObjectId -from flask import Blueprint, g, redirect, render_template, request, abort -from flask_wtf import FlaskForm -from pymongo import MongoClient -from wtforms import StringField, IntegerField, SelectField, BooleanField, DateTimeField, validators -from wtforms.validators import DataRequired - -import pytz import const -from common import slack_post, User +import pytz +from bson.objectid import ObjectId +from common import User +from dateutil.parser import ParserError, parse +from flask import Blueprint, abort, g, redirect, render_template, request +from flask_wtf import FlaskForm from oidc import login_required, read_user +from pymongo import MongoClient +from wtforms import (BooleanField, IntegerField, SelectField, StringField, + validators) +from wtforms.validators import DataRequired page_doorboy = Blueprint("doorboy", __name__) db = MongoClient(const.MONGO_URI).get_default_database() @@ -27,7 +27,7 @@ def view_doorboy_claim(event_id): }) # Find token object to associate with user - token = db.inventory.update_one({ + db.inventory.update_one({ "type": "token", "token.uid_hash": event["token"]["uid_hash"], "inventory.owner.username": { "$exists": False } @@ -127,35 +127,27 @@ class HoldDoorForm(FlaskForm): door_name = SelectField("Door name", choices=[(j,j) for j in ["grounddoor", "frontdoor", "backdoor"]], validators=[DataRequired()]) duration = IntegerField('Duration in seconds', validators=[DataRequired(), validators.NumberRange(min=5, max=21600)]) +# duration=0 to override and close right away @page_doorboy.route("/m/doorboy/hold", methods=["POST"]) @login_required def view_doorboy_hold(): user = read_user() form = HoldDoorForm(request.form) - now = datetime.utcnow() if form.validate_on_submit(): db.eventlog.insert_one({ "component": "doorboy", - "type": "hold", - "requester": user["name"], + "method": "hold", + "timestamp": datetime.utcnow(), "door": form.door_name.data, + "approved": True, + "user": { + "id": user["username"], + "name": user["name"] + }, "expires": datetime.utcnow() + timedelta(seconds=form.duration.data) }) return redirect("/m/doorboy") -@page_doorboy.route("/m/doorboy/slam", methods=["POST"]) -@login_required -def view_doorboy_slam(): - user = read_user() - db.eventlog.insert_one({ - "component": "doorboy", - "type": "hold", - "requester": user["name"], - "door": form.door_name.data, - "expires": datetime.utcnow() + timedelta(minutes=form.duration_min.data) - }) - return redirect("/m/doorboy") - @page_doorboy.route("/m/doorboy//open") @login_required def view_doorboy_open(door): @@ -169,22 +161,17 @@ def view_doorboy_open(door): access_group = "k-space:floor" approved = access_group in g.users_lookup.get(user["username"], User()).groups db.eventlog.insert_one({ - "method": "web", - "approved": approved, - "duration": 5, "component": "doorboy", - "type": "open-door", - "door": door, - "member_id": user["username"], - "member": user["name"], + "method": "web", "timestamp": datetime.utcnow(), + "door": door, + "approved": approved, + "user": { + "id": user["username"], + "name": user["name"] + } }) - status = "Permitted" if approved else "Denied" - subject = user["name"] - msg = "%s %s door access for %s via https://inventory.k-space.ee/m/doorboy" % (status, door, subject) - slack_post(msg, "doorboy") - if approved: return redirect("/m/doorboy") else: