move /cards to doorboy-proxy + refactor mongo format
This commit is contained in:
@@ -23,12 +23,8 @@ spec:
|
|||||||
env:
|
env:
|
||||||
- name: OIDC_USERS_NAMESPACE
|
- name: OIDC_USERS_NAMESPACE
|
||||||
value: "default"
|
value: "default"
|
||||||
- name: SLACK_DOORLOG_CALLBACK
|
|
||||||
value: "changeme"
|
|
||||||
- name: SLACK_VERIFICATION_TOKEN
|
- name: SLACK_VERIFICATION_TOKEN
|
||||||
value: "changeme"
|
value: "changeme"
|
||||||
- name: INVENTORY_API_KEY
|
|
||||||
value: "sptWL6XFxl4b8"
|
|
||||||
- name: PYTHONUNBUFFERED
|
- name: PYTHONUNBUFFERED
|
||||||
value: "1"
|
value: "1"
|
||||||
# Google test key
|
# Google test key
|
||||||
|
@@ -1,57 +1,14 @@
|
|||||||
import re
|
|
||||||
import const
|
|
||||||
import time
|
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import wraps
|
|
||||||
|
import const
|
||||||
|
from flask import Blueprint, g, request
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from flask import Blueprint, g, request, jsonify
|
|
||||||
from common import slack_post
|
|
||||||
|
|
||||||
page_api = Blueprint("api", __name__)
|
page_api = Blueprint("api", __name__)
|
||||||
db = MongoClient(const.MONGO_URI).get_default_database()
|
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'])
|
@page_api.route("/api/slack/doorboy", methods=['POST'])
|
||||||
def view_slack_doorboy():
|
def view_slack_doorboy():
|
||||||
begin_time = time.perf_counter()
|
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()
|
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()
|
end_time = time.perf_counter()
|
||||||
print(f"view_slack_doorboy done in {end_time - begin_time:.4f} seconds")
|
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
|
return return_message
|
||||||
|
|
||||||
def handle_slack_door_event(doors, approved, member, door, status, subject):
|
def handle_slack_door_event(doors, approved, member, door, status, subject):
|
||||||
begin_time = time.perf_counter()
|
begin_time = time.perf_counter()
|
||||||
for d in doors:
|
for d in doors:
|
||||||
db.eventlog.insert_one({
|
db.eventlog.insert_one({
|
||||||
"method": "slack",
|
|
||||||
"approved": approved,
|
|
||||||
"duration": 5,
|
|
||||||
"component": "doorboy",
|
"component": "doorboy",
|
||||||
"type": "open-door",
|
"method": "slack",
|
||||||
"door": d,
|
|
||||||
"member_id": member.username,
|
|
||||||
"member": member.display_name,
|
|
||||||
"timestamp": datetime.utcnow(),
|
"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()
|
end_time = time.perf_counter()
|
||||||
print(f"handle_slack_door_event done in {end_time - begin_time:.4f} seconds")
|
print(f"handle_slack_door_event done in {end_time - begin_time:.4f} seconds")
|
||||||
|
@@ -1,17 +1,16 @@
|
|||||||
import os
|
|
||||||
import collections.abc
|
import collections.abc
|
||||||
from functools import wraps
|
import os
|
||||||
|
|
||||||
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
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from functools import wraps
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import const
|
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"
|
devenv = const.ENVIRONMENT_TYPE == "DEV"
|
||||||
OIDC_USERS_NAMESPACE = os.getenv("OIDC_USERS_NAMESPACE")
|
OIDC_USERS_NAMESPACE = os.getenv("OIDC_USERS_NAMESPACE")
|
||||||
@@ -93,18 +92,6 @@ def flatten(d, parent_key='', sep='.'):
|
|||||||
items.append((new_key, v))
|
items.append((new_key, v))
|
||||||
return dict(items)
|
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={}):
|
def build_query(base_query, fields=[], sort_fields={}):
|
||||||
top_usernames= ['k-space']
|
top_usernames= ['k-space']
|
||||||
selectors = []
|
selectors = []
|
||||||
|
@@ -18,6 +18,4 @@ BUCKET_NAME = os.environ["BUCKET_NAME"]
|
|||||||
INVENTORY_ASSETS_BASE_URL = os.environ["INVENTORY_ASSETS_BASE_URL"]
|
INVENTORY_ASSETS_BASE_URL = os.environ["INVENTORY_ASSETS_BASE_URL"]
|
||||||
MONGO_URI = os.environ["MONGO_URI"]
|
MONGO_URI = os.environ["MONGO_URI"]
|
||||||
SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"] # used to verify (deprecated) incoming requests from slack
|
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"]
|
MACADDRESS_OUTLINK_BASEURL = os.environ["MACADDRESS_OUTLINK_BASEURL"]
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
from datetime import datetime, timedelta
|
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
|
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 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__)
|
page_doorboy = Blueprint("doorboy", __name__)
|
||||||
db = MongoClient(const.MONGO_URI).get_default_database()
|
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
|
# Find token object to associate with user
|
||||||
token = db.inventory.update_one({
|
db.inventory.update_one({
|
||||||
"type": "token",
|
"type": "token",
|
||||||
"token.uid_hash": event["token"]["uid_hash"],
|
"token.uid_hash": event["token"]["uid_hash"],
|
||||||
"inventory.owner.username": { "$exists": False }
|
"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()])
|
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 = 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"])
|
@page_doorboy.route("/m/doorboy/hold", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def view_doorboy_hold():
|
def view_doorboy_hold():
|
||||||
user = read_user()
|
user = read_user()
|
||||||
form = HoldDoorForm(request.form)
|
form = HoldDoorForm(request.form)
|
||||||
now = datetime.utcnow()
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
db.eventlog.insert_one({
|
db.eventlog.insert_one({
|
||||||
"component": "doorboy",
|
"component": "doorboy",
|
||||||
"type": "hold",
|
"method": "hold",
|
||||||
"requester": user["name"],
|
"timestamp": datetime.utcnow(),
|
||||||
"door": form.door_name.data,
|
"door": form.door_name.data,
|
||||||
|
"approved": True,
|
||||||
|
"user": {
|
||||||
|
"id": user["username"],
|
||||||
|
"name": user["name"]
|
||||||
|
},
|
||||||
"expires": datetime.utcnow() + timedelta(seconds=form.duration.data)
|
"expires": datetime.utcnow() + timedelta(seconds=form.duration.data)
|
||||||
})
|
})
|
||||||
return redirect("/m/doorboy")
|
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/<door>/open")
|
@page_doorboy.route("/m/doorboy/<door>/open")
|
||||||
@login_required
|
@login_required
|
||||||
def view_doorboy_open(door):
|
def view_doorboy_open(door):
|
||||||
@@ -169,22 +161,17 @@ def view_doorboy_open(door):
|
|||||||
access_group = "k-space:floor"
|
access_group = "k-space:floor"
|
||||||
approved = access_group in g.users_lookup.get(user["username"], User()).groups
|
approved = access_group in g.users_lookup.get(user["username"], User()).groups
|
||||||
db.eventlog.insert_one({
|
db.eventlog.insert_one({
|
||||||
"method": "web",
|
|
||||||
"approved": approved,
|
|
||||||
"duration": 5,
|
|
||||||
"component": "doorboy",
|
"component": "doorboy",
|
||||||
"type": "open-door",
|
"method": "web",
|
||||||
"door": door,
|
|
||||||
"member_id": user["username"],
|
|
||||||
"member": user["name"],
|
|
||||||
"timestamp": datetime.utcnow(),
|
"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:
|
if approved:
|
||||||
return redirect("/m/doorboy")
|
return redirect("/m/doorboy")
|
||||||
else:
|
else:
|
||||||
|
Reference in New Issue
Block a user