play-api/api/__init__.py

224 lines
8.4 KiB
Python

import configparser
import json
import magic
import os
import re
import shutil
import uuid
from flask import Flask, Response, request
from flask import redirect, url_for
from werkzeug.utils import safe_join
from api.auth import Authenticator, BackendException
import api.daemon_detect
def create_app():
app = Flask(__name__)
app.root_path, _ = os.path.split(app.root_path)
config = configparser.ConfigParser()
config.read(os.path.join(app.root_path, 'config.ini'))
queuedir = os.path.join(app.root_path, 'queue')
errordir = os.path.join(app.root_path,'error')
storagedir = os.path.join(app.root_path, 'storage')
app.logger.setLevel(config['api'].get('log_level', 'ERROR'))
authenticator = Authenticator(config, app.logger)
# Flask apparently breaks mod_xsendfile by sending too many
# headers if you use the built-in flask.send_file() with
# app.use_x_sendfile=True, hence this helper instead.
def send_file(filepath, extra_headers={}):
r = Response(status=200)
r.headers['X-Sendfile'] = filepath
if extra_headers:
for key, value in extra_headers.items():
r.headers[key] = value
return r
@app.route('/')
def base():
return Response(response='REST')
@app.route('/login')
def login():
return redirect(request.args.get('next'))
@app.route('/presentation')
def list_presentations():
presentations = os.listdir(storagedir)
return Response(response=json.dumps(presentations),
content_type='application/json')
@app.route('/presentation/<string:presentation>', methods=['GET'])
def get_presentation(presentation):
path = os.path.join(storagedir, presentation, 'package.json')
try:
with open(path, 'r') as p:
package = p.read()
#for some reason a missing file doesn't give the appropriate error
except PermissionError:
return Response(response='Not found', status_code=404)
return Response(response=package,
content_type='application/json')
@app.route('/presentation/<string:presentation>', methods=['DELETE'])
def delete_presentation(presentation):
data = request.get_json()
if 'Authorization' in request.headers:
ok = 'authorized'
result = _authenticate(request.headers['Authorization'], ok)
if result != ok:
return result
elif ('auth' not in data
or config['api']['password'] != data['auth']):
m = "Use correct credentials to access this endpoint"
return Response(response=m, status=401)
return _delete(presentation)
@app.route('/presentation/<string:presentation>/<path:path>')
def serve_file(presentation, path):
realpath = safe_join(storagedir, os.path.join(presentation, path))
mimetype = None
try:
mimetype = magic.from_file(realpath, mime=True)
except FileNotFoundError as e:
return Response(status=404)
if mimetype.startswith('image/'):
return send_file(realpath)
return _authenticate(request.args.get('token'),
send_file(realpath))
@app.route('/notify', methods=['POST', 'GET'])
def notify(suf=''):
return Response(response="Deprecated endpoint. "
"Use /notify/{default|cattura|mediasite} instead.")
@app.route('/notify/default', methods=['POST'])
def notify_default():
return Response(response=_enqueue({'type': 'default',
'data': request.json}))
@app.route('/notify/mediasite', methods=['POST'])
def notify_mediasite():
return Response(response=_enqueue({'type': 'mediasite',
'data': request.json}))
@app.route('/notify/cattura', methods=['POST'])
def notify_cattura():
indata = request.form
result = indata['eventType']
if result != 'PUBLISH_SUCCESS':
_log_publish_failure(indata['eventSource'], indata['eventData'])
return Response(response='Error logged')
queuepackage = {'type': 'cattura',
'recorder': indata['eventSource'],
'data': json.loads(indata['eventData'])}
result = _enqueue(queuepackage)
return Response(response=result)
@app.route('/notify/arec', methods=['POST'])
def notify_arec():
indata = request.form
upload_dir = indata['upload_dir']
queuepackage = {'type': 'arec',
'upload_dir': upload_dir}
result = _enqueue(queuepackage)
return Response(response=result)
@app.route('/status/daemon')
def daemon_status():
running = False
if api.daemon_detect.is_running():
running = True
return Response(response=json.dumps({'running': running}),
content_type='application/json')
@app.route('/status/queue', methods=['GET'])
def status_queue():
def path_filter(path):
if not re.match(r'^[0-9a-f-]+$', os.path.basename(path)):
return False
if not os.path.isfile(path):
return False
return True
os_queue_list = filter(path_filter,
[os.path.join(queuedir, item)
for item in os.listdir(queuedir)])
os_error_list = os.listdir(errordir)
queue_list = []
for path in sorted(os_queue_list, key=os.path.getctime):
item = os.path.basename(path)
with open(path, 'r') as f:
data = json.load(f)
mydata = {'type': data['type'],
'id': item,
'uploaded': os.path.getctime(path)}
if data['type'] == 'cattura':
mydata['recorder'] = data['recorder']
mydata['title'] = data['data']['mediaPackageID']
else:
mydata['title'] = data['data']['title']
mydata['description'] = data['data']['description']
mydata['courses'] = data['data']['courses']
mydata['presenters'] = data['data']['presenters']
mydata['tags'] = data['data']['tags']
mydata['created'] = data['data']['created']
if item in os_error_list:
with open(os.path.join(errordir, item), 'r') as e:
mydata['error'] = e.read()
queue_list.append(mydata)
return Response(response=json.dumps(queue_list),
content_type='application/json')
def _authenticate(token, success_response):
if token:
try:
result = authenticator.authenticate(token)
except BackendException as e:
return Response(response=f'{e.status}: {e.message}',
status=502)
if result == 'granted':
return success_response
elif result == 'invalid':
return Response(status=205)
return Response(response='Unauthorized', status=403)
def _delete(presentation):
path = os.path.join(storagedir, presentation)
if os.path.exists(path):
shutil.rmtree(path)
return Response(response=f'Deleted {presentation}')
return Response(response=f'{presentation} not found', status=404)
def _enqueue(package, suf=''):
uuid = _get_uuid()
filename = f'{uuid}{suf}'
tempname = f'{filename}.tmp'
# write to a temporary file first in order to avoid races.
# the daemon will ignore files with an extension
with open(os.path.join(queuedir, tempname), 'x') as f:
f.write(json.dumps(package))
# move to the final location with an atomic move
os.rename(os.path.join(queuedir, tempname),
os.path.join(queuedir, filename))
return uuid
def _get_uuid():
candidate = str(uuid.uuid4())
for path in [os.path.join(p, candidate)
for p in [queuedir, storagedir]]:
if os.path.exists(path):
return _get_uuid()
return candidate
def _log_publish_failure(source, data):
app.logger.warning('%s failed to publish %s', source, data)
return app