moved everything to a new module called backend

This commit is contained in:
2019-10-23 15:00:33 +02:00
parent 310d5f4820
commit 6b4f7c8118
52 changed files with 2 additions and 380 deletions

78
backend/api/__init__.py Normal file
View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from flask import Blueprint, abort
from flask_restplus import Api, Namespace
api_authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
},
'basicAuth': {
'type': 'basic',
'scheme': 'basic'
},
'bearerAuth': {
'type': 'apiKey',
'scheme': 'bearer',
'name': 'Authorization',
'in': 'header'
}
}
api_bp = Blueprint('api', __name__, url_prefix='/api')
api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API',
description='The Vue Test API', doc='/v1/doc/', authorizations=api_authorizations, security='bearerAuth')
api_user = Namespace('user', description="User management namespace", authorizations=api_authorizations)
api_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations)
api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations)
api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations)
api_virtual_command = Namespace('virtual_command', description="Virtual command namespace",
authorizations=api_authorizations)
api_cron_job = Namespace('cron_job', description="Cron job namespace",
authorizations=api_authorizations)
api_control = Namespace('control', description="Control namespace",
authorizations=api_authorizations)
api_v1.add_namespace(api_user)
api_v1.add_namespace(api_group)
api_v1.add_namespace(api_room)
api_v1.add_namespace(api_recorder)
api_v1.add_namespace(api_virtual_command)
api_v1.add_namespace(api_cron_job)
api_v1.add_namespace(api_control)
auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth')
auth_api_v1 = Api(auth_api_bp, prefix="/v1", version='0.1', title='Auth API',
description='Auth API', doc='/v1/doc/', authorizations=api_authorizations, security='bearerAuth')
auth_api_providers_ns = Namespace('providers')
auth_api_register_ns = Namespace('register')
auth_api_v1.add_namespace(auth_api_providers_ns)
auth_api_v1.add_namespace(auth_api_register_ns)
# user_api_bp = Blueprint('user_api', __name__, url_prefix='/api/user')
# group_api_bp = Blueprint('group_api', __name__, url_prefix='/api/group')
from .example_api import *
from .auth_api import *
from .user_api import *
from .group_api import *
from .room_api import *
from .recorder_api import *
from .control_api import *
from .virtual_command_api import *
# from .group_api import *
@api_bp.route('/<path:path>')
def catch_all_api(path):
"""
Default 404 response for undefined paths in API.
:param path:
:return:
"""
abort(404)

196
backend/api/auth_api.py Normal file
View File

@@ -0,0 +1,196 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import base64
import json
from pprint import pprint
import flask
from datetime import datetime, timedelta
import jwt
from flask import request, jsonify, current_app, url_for, Response, session, redirect, make_response
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity, \
get_raw_jwt, jwt_required
from functools import wraps
from random import randint
from flask_login import logout_user, login_user
from typing import Iterable
from flask_restplus import Resource, fields
from werkzeug.routing import BuildError
from backend import db, app, jwt_extended
from backend.api import auth_api_bp, auth_api_providers_ns, auth_api_register_ns
from backend.auth import AUTH_PROVIDERS, oidc_auth
from backend.models.user_model import User, Group, BlacklistToken
@auth_api_bp.route('/providers', methods=('GET',))
def get_auth_providers():
providers = dict()
for p in AUTH_PROVIDERS:
provider = dict(AUTH_PROVIDERS[p])
try:
provider["url"] = url_for(AUTH_PROVIDERS[p]["url"])
except BuildError:
provider["url"] = AUTH_PROVIDERS[p]["url"]
providers[p] = provider
return jsonify(providers)
@auth_api_providers_ns.route('/')
class AuthProviders(Resource):
def get(self):
return get_auth_providers()
@auth_api_bp.route('/register', methods=('POST',))
def register():
data = request.get_json()
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
@auth_api_register_ns.route('/')
@auth_api_register_ns.expect(auth_api_register_ns.model('RegisterModel', {
'nickname': fields.String(required=False, description='The user\'s nickname'),
'first_name': fields.String(required=False, description='The user\'s first name'),
'last_name': fields.String(required=False, description='The user\'s last name'),
'lang': fields.String(required=False, description='The user\'s preferred language'),
'timezone': fields.String(required=False, description='The user\'s preferred timezone'),
'email': fields.String(required=True, description='The user\'s e-mail address'),
'password': fields.String(required=False, description='The group\'s name')
}))
class AuthProviders(Resource):
def get(self):
return register()
@auth_api_bp.route('/login', methods=('GET', 'POST',))
def login():
print("login")
print(request)
data = request.get_json()
if not data:
return jsonify({'message': 'Invalid request data', 'authenticated': False}), 401
print(data)
user = User.authenticate(**data)
if not user:
return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401
token = {
'access_token': create_access_token(identity=user, fresh=True),
'refresh_token': create_refresh_token(identity=user)
}
return jsonify(token), 200
# Endpoint for revoking the current users access token
@auth_api_bp.route('/logout', methods=['GET', 'DELETE'])
@jwt_required
def logout():
jti = get_raw_jwt()['jti']
db.session.add(BlacklistToken(token=jti))
db.session.commit()
return jsonify({"msg": "Successfully logged out"}), 200
# Endpoint for revoking the current users refresh token
@auth_api_bp.route('/logout2', methods=['GET', 'DELETE'])
@jwt_refresh_token_required
def logout2():
jti = get_raw_jwt()['jti']
db.session.add(BlacklistToken(token=jti))
db.session.commit()
return jsonify({"msg": "Successfully logged out"}), 200
def check_and_create_groups(groups: Iterable[str]):
user_groups = []
for g in groups:
group = Group.get_by_name(g)
if group is None:
group = Group(name=g)
db.session.add(group)
user_groups.append(group)
db.session.commit()
return user_groups
def create_or_retrieve_user_from_userinfo(userinfo):
try:
email = userinfo["email"]
except KeyError:
return None
user_groups = check_and_create_groups(groups=userinfo.get("memberOf", []))
user = User.get_by_identifier(email)
if user is not None:
app.logger.info("user found -> update user")
pprint(user.to_dict())
user.first_name = userinfo.get("given_name", "")
user.last_name = userinfo.get("family_name", "")
for g in user_groups:
user.groups.append(g)
db.session.commit()
return user
user = User(email=email, first_name=userinfo.get("given_name", ""),
last_name=userinfo.get("family_name", ""), external_user=True,
groups=user_groups)
app.logger.info("creating new user")
db.session.add(user)
db.session.commit()
return user
@auth_api_bp.route('/oidc', methods=['GET'])
@auth_api_bp.route('/oidc/<redirect_url>', methods=['GET'])
@oidc_auth.oidc_auth()
def oidc(redirect_url=None):
user = create_or_retrieve_user_from_userinfo(flask.session['userinfo'])
if user is None:
return "Could not authenticate: could not find or create user.", 401
if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False):
token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY'])
else:
token = json.dumps({
'access_token': create_access_token(identity=user, fresh=True),
'refresh_token': create_refresh_token(identity=user)
})
if redirect_url is None:
redirect_url = request.headers.get("Referer")
if redirect_url is None:
redirect_url = request.args.get('redirect_url')
if redirect_url is None:
redirect_url = "/"
app.logger.info("Token: {}".format(token))
response = make_response(redirect(redirect_url))
response.set_cookie('tokens', base64.b64encode(token.encode('utf-8')))
return response
@auth_api_bp.route('/refresh', methods=['GET'])
@jwt_refresh_token_required
def refresh():
"""Refresh token endpoint. This will generate a new access token from
the refresh token, but will mark that access token as non-fresh,
as we do not actually verify a password in this endpoint."""
jwt_identity = get_jwt_identity()
user = User.get_by_identifier(jwt_identity)
app.logger.info("Refreshing token for " + str(user))
new_token = create_access_token(identity=user, fresh=False)
ret = {'access_token': new_token}
return jsonify(ret), 200

View File

@@ -0,0 +1,42 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import json
from datetime import datetime
from flask_jwt_extended import jwt_required, get_current_user, get_jwt_claims
from flask_restplus import fields, Resource
from backend import db
from backend.api import api_control, get_jwt_identity
control_command_response_model = api_control.model('Control Command Response', {
'time': fields.DateTime(required=False, description='Creation date of the recorder'),
'state': fields.String(min_length=3, required=True, description='The recorder\'s name'),
'output': fields.String(required=False, description='The recorder\'s description'),
'error': fields.String(required=False, description='The recorder\'s description'),
})
@api_control.route('')
class ControlCommand(Resource):
control_command_parser = api_control.parser()
control_command_parser.add_argument('recorder_id', type=int, default=1, required=True)
control_command_parser.add_argument('command_id', type=int, default=1, required=True)
control_command_parser.add_argument('parameters', default=json.dumps({'p1': 'v1'}), type=dict, required=False,
location='json')
@jwt_required
@api_control.doc('run_command')
@api_control.expect(control_command_parser)
@api_control.marshal_with(control_command_response_model, skip_none=False, code=201)
def post(self):
print(get_current_user())
print(get_jwt_identity())
current_user = {'user': get_current_user(), 'claims': get_jwt_claims()}
args = self.control_command_parser.parse_args()
return {'time': datetime.utcnow(), 'output': args, 'state': current_user}

134
backend/api/example_api.py Normal file
View File

@@ -0,0 +1,134 @@
import datetime
import ipaddress
import json
import logging
from random import *
from flask import jsonify, Blueprint, request
from flask_restplus import Resource, reqparse
from backend import basic_auth, multi_auth, db, jwt_auth
from backend.api import api_v1, api_bp
@api_bp.route('/random')
def random_number():
"""
:return: a random number
"""
response = {
'randomNumber': randint(1, 100)
}
return jsonify(response)
@api_bp.route('/test_jwt')
@jwt_auth.login_required
def random_number_jwt():
"""
:return: a random number
"""
response = {
'randomNumber': randint(1, 100)
}
return jsonify(response)
class HelloWorld(Resource):
"""
This is a test class.
"""
def get(self):
"""
just a test!
:return: Hello: World
"""
print(str(self))
return {'hello': 'world'}
api_v1.add_resource(HelloWorld, '/')
class SensorData_Handler(Resource):
parser = reqparse.RequestParser()
parser.add_argument('values', type=str, required=True, help="sensor data values are required (as JSON list)")
@basic_auth.login_required
def get(self, mac):
sensor = Sensor.get_by_mac_user(mac, g.user)
if not sensor:
return "sensor not found", 404
return jsonify(sensor.get_sensor_data())
# return {'task': 'id: ' + mac}
@multi_auth.login_required
def post(self, mac):
sensor = Sensor.get_by_mac(mac)
if not sensor:
return "sensor not found", 404
print("JSON")
print(request.json)
print(request.json['values'])
args = SensorData_Handler.parser.parse_args()
print("values...")
print(args['values'])
values = json.loads(args['values'])
app.logger.info("vals: " + str(values) + " (len: " + str(len(values)) + ")")
rough_geo_location = None
try:
rough_geo_location_info = None
ip = ipaddress.ip_address(request.remote_addr)
if request.remote_addr == "127.0.0.1":
ip = ipaddress.ip_address("89.245.37.108")
if isinstance(ip, ipaddress.IPv4Address):
rough_geo_location_info = geoip4.record_by_addr(str(ip))
elif isinstance(ip, ipaddress.IPv6Address):
rough_geo_location_info = geoip6.record_by_addr(str(ip))
if rough_geo_location_info is not None:
rough_geo_location = json.dumps({key: rough_geo_location_info[key] for key in ['country_code',
'country_name',
'postal_code',
'city',
'time_zone',
'latitude',
'longitude']
})
except ValueError:
pass
for values_at_time in values:
wasss_app.logger.info("values_at_time: " + str(values_at_time) + " (len: " + str(len(values_at_time)) + ")")
sensor_data = SensorData(sensor=sensor, rough_geo_location=rough_geo_location)
val_cycle = cycle(values_at_time)
for sensor_data_header in sensor.get_sensor_data_headers():
value = next(val_cycle)
if sensor_data_header == SensorData.HEADER_TIMESTAMP:
sensor_data.timestamp = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
wasss_app.logger.info(sensor_data.timestamp)
elif sensor_data_header == SensorData.HEADER_VOLTAGE:
sensor_data.voltage = value
elif sensor_data_header == SensorData.HEADER_TEMPERATURE:
sensor_data.temp = value
elif sensor_data_header == SensorData.HEADER_HUMIDITY:
sensor_data.humidity = value
elif sensor_data_header == SensorData.HEADER_PRESSURE:
sensor_data.pressure = value
elif sensor_data_header == SensorData.HEADER_BRIGHTNESS:
sensor_data.brightness = value
elif sensor_data_header == SensorData.HEADER_SPEED:
sensor_data.speed = value
elif sensor_data_header == SensorData.HEADER_MOTION:
sensor_data.motion = value
else:
gen_sen_data = GenericSensorData(sensor=sensor, name=sensor_data_header, value=value)
db.session.add(sensor_data)
db.session.commit()
return jsonify("ok")
api_v1.add_resource(SensorData_Handler, '/sensor/<string:mac>/data')

87
backend/api/group_api.py Normal file
View File

@@ -0,0 +1,87 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
from flask_jwt_extended import jwt_required
from flask_restplus import fields, Resource
from backend import db
from backend.api import api_group
from backend.models.user_model import Group
group_model = api_group.model('Group', {
'id': fields.String(required=False, description='The group\'s identifier'),
'name': fields.String(required=True, description='The group\'s name'),
'description': fields.String(required=False, description='The group\'s description'),
'users': fields.List(fields.Nested(api_group.model('group_member',
{'id': fields.Integer(), 'nickname': fields.String(),
'first_name': fields.String(), 'last_name': fields.String(),
'email': fields.String(), 'registered_on': fields.DateTime(),
'last_seen': fields.DateTime()})),
required=False, description='Group members.')
})
@api_group.route('/<int:id>')
@api_group.response(404, 'Group not found')
@api_group.param('id', 'The group identifier')
class GroupResource(Resource):
@jwt_required
@api_group.doc('get_group')
@api_group.marshal_with(group_model)
def get(self, id):
"""Fetch a user given its identifier"""
group = Group.get_by_id(id)
if group is not None:
return group
api_group.abort(404)
@jwt_required
@api_group.doc('delete_todo')
@api_group.response(204, 'Todo deleted')
def delete(self, id):
'''Delete a task given its identifier'''
group = Group.get_by_id(id)
if group is not None:
group.delete()
return '', 204
api_group.abort(404)
@jwt_required
@api_group.doc('update_group')
@api_group.expect(group_model)
@api_group.marshal_with(group_model)
def put(self, id):
'''Update a task given its identifier'''
group = Group.get_by_id(id)
if group is not None:
group.name = api_group["name"]
db.session.commit()
return group
api_group.abort(404)
@api_group.route('')
class GroupList(Resource):
@jwt_required
@api_group.doc('groups')
@api_group.marshal_list_with(group_model)
def get(self):
"""
List all groups
:return: groups
"""
return Group.get_all()
@jwt_required
@api_group.doc('create_group')
@api_group.expect(group_model)
@api_group.marshal_with(group_model, code=201)
def post(self):
group = Group(**api_group.payload)
db.session.add(group)
db.session.commit()
return group

275
backend/api/recorder_api.py Normal file
View File

@@ -0,0 +1,275 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
from datetime import datetime
from pprint import pprint
from flask_jwt_extended import jwt_required
from flask_restplus import fields, Resource, inputs
from backend import db, app
from backend.api import api_recorder
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room
import backend.recorder_adapters as r_a
recorder_model = api_recorder.model('Recorder', {
'id': fields.String(required=False, description='The recorder\'s identifier'),
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
'last_time_modified': fields.DateTime(required=False, description='Creation date of the recorder'),
'name': fields.String(min_length=3, required=True, description='The recorder\'s name'),
'description': fields.String(required=False, description='The recorder\'s description'),
'locked': fields.Boolean(required=False, description='Indicates whether the recorder settings can be altered'),
'lock_message': fields.String(required=False, description='Optional: message explaining lock state'),
'offline': fields.Boolean(required=False,
description='Should be set when recorder is disconnected for maintenance, etc.'),
'ip': fields.String(required=False, description='The recorder\'s IP address'),
'network_name': fields.String(required=False, description='The recorder\'s network name'),
'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'),
'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'),
# 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False,
# description='If this is set, telnet will be used instead of ssh. '
# 'This might require specific commands.'),
'recorder_model': fields.Nested(api_recorder.model('recorder_model',
{'id': fields.Integer(),
'name': fields.String(attribute="model_name", )}),
required=False,
allow_null=True,
skip_none=False,
description='Model of the recorder.'),
'room': fields.Nested(api_recorder.model('recorder_room',
{'id': fields.Integer(), 'name': fields.String(),
'number': fields.String(), 'alternate_name': fields.String()}),
r0equired=False,
allow_null=True,
skip_none=False,
description='Room in which the recorder is located.'),
'virtual_commands': fields.List(fields.Nested(api_recorder.model('recorder_virtual_commands',
{'id': fields.Integer(),
'name': fields.String()})))
})
recorder_command_model = api_recorder.model('Recorder Command', {
'id': fields.String(required=False, description='The recorder command\'s identifier'),
'name': fields.String(required=True, description='The recorder command\'s name'),
'alternative_name': fields.String(required=False, description='The recorder command\'s alternative name'),
'disabled': fields.Boolean(required=False, description='Indicates if the recorder command is disabled'),
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
'last_time_modified': fields.DateTime(required=False),
'description': fields.String(required=False, description='The recorder command\'s description'),
'parameters': fields.Raw(required=True, description='The recorder parameters'),
'recorder_model': fields.Nested(api_recorder.model('recorder_command_models',
{'id': fields.Integer(),
'name': fields.String(attribute="model_name", )})),
})
recorder_model_model = api_recorder.model('Recorder Model', {
'id': fields.String(required=False, description='The recorder model\'s identifier'),
'name': fields.String(attribute="model_name", required=True, description='The recorder model\'s name'),
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
'last_time_modified': fields.DateTime(required=False),
'notes': fields.String(required=False, description='The recorder model\'s notes'),
'requires_username': fields.Boolean(),
'requires_password': fields.Boolean(),
'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model',
{'id': fields.Integer(),
'name': fields.String(attribute="model_name", ),
'network_name': fields.String(),
'ip': fields.String()})), required=False,
description='Model of the recorder.'),
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
})
# ==
@api_recorder.route('/<int:id>')
@api_recorder.response(404, 'Recorder not found')
@api_recorder.param('id', 'The recorder identifier')
class RecorderResource(Resource):
@jwt_required
@api_recorder.doc('get_recorder')
@api_recorder.marshal_with(recorder_model, skip_none=False)
def get(self, id):
"""Fetch a recorder given its identifier"""
recorder = Recorder.query.get(id)
if recorder is not None:
return recorder
api_recorder.abort(404)
@jwt_required
@api_recorder.doc('delete_todo')
@api_recorder.response(204, 'Todo deleted')
def delete(self, id):
"""Delete a recorder given its identifier"""
recorder = Recorder.query.get(id)
if recorder is not None:
db.session.delete(recorder)
db.session.commit()
return '', 204
api_recorder.abort(404)
recorder_update_parser = api_recorder.parser()
recorder_update_parser.add_argument('name', type=str, required=False, nullable=False, store_missing=False)
recorder_update_parser.add_argument('network_name', type=inputs.regex(inputs.netloc_regex), required=False, store_missing=False)
recorder_update_parser.add_argument('ip', type=inputs.ipv4, required=False, store_missing=False)
recorder_update_parser.add_argument('ip6', type=inputs.ipv6, required=False, store_missing=False)
recorder_update_parser.add_argument('ssh_port', type=inputs.int_range(0,65535), required=False, default=22, store_missing=False)
recorder_update_parser.add_argument('telnet_port', type=inputs.int_range(0,65535), required=False, default=23, store_missing=False)
recorder_update_parser.add_argument('room_id', type=int, required=False, store_missing=False)
recorder_update_parser.add_argument('offline', type=inputs.boolean, required=False, default=False, store_missing=False)
recorder_update_parser.add_argument('locked', type=inputs.boolean, required=False, default=False, store_missing=False)
recorder_update_parser.add_argument('lock_message', type=str, required=False, nullable=True, default=None,
store_missing=False)
recorder_update_parser.add_argument('model_id', type=int, required=False, store_missing=False)
recorder_update_parser.add_argument('description', type=str, required=False, nullable=True, default=None,
store_missing=False)
recorder_update_parser.add_argument('virtual_command_ids', action='split', nullable=True, default=[],
required=False, store_missing=False)
@jwt_required
@api_recorder.doc('update_recorder')
@api_recorder.expect(recorder_model)
def put(self, id):
"""Update a recorder given its identifier"""
args = self.recorder_update_parser.parse_args(strict=True)
args['last_time_modified'] = datetime.utcnow()
pprint(args)
num_rows_matched = Recorder.query.filter_by(id=id).update(args)
print(num_rows_matched)
if num_rows_matched < 1:
api_recorder.abort(404)
db.session.commit()
return "ok"
@api_recorder.route('')
class RecorderList(Resource):
@jwt_required
@api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model, skip_none=False)
def get(self):
"""
List all recorders
:return: recorders
"""
return Recorder.get_all()
@jwt_required
@api_recorder.doc('create_recorder')
@api_recorder.expect(recorder_model)
@api_recorder.marshal_with(recorder_model, skip_none=False, code=201)
def post(self):
if "room_id" in api_recorder.payload:
if api_recorder.payload["room_id"] is None:
api_recorder.payload["room"] = None
else:
room = Room.query.get(api_recorder.payload["room_id"])
if room is not None:
api_recorder.payload["room"] = room
else:
return "specified room (id: {}) does not exist!".format(api_recorder.payload["room_id"]), 404
if "recorder_model_id" in api_recorder.payload:
if api_recorder.payload["recorder_model_id"] is None:
api_recorder.payload["recorder_model"] = None
else:
rec_model = RecorderModel.query.get(api_recorder.payload["recorder_model_id"])
if rec_model is not None:
api_recorder.payload["recorder_model"] = rec_model
else:
return "specified recorder model (id: {}) does not exist!".format(
api_recorder.payload["recorder_model_id"]), 404
recorder = Recorder(**api_recorder.payload)
db.session.add(recorder)
db.session.commit()
return recorder
@api_recorder.route('/model/<int:id>')
@api_recorder.response(404, 'Recorder Model not found')
@api_recorder.param('id', 'The recorder model identifier')
class RecorderModelResource(Resource):
@jwt_required
@api_recorder.doc('get_recorder_model')
@api_recorder.marshal_with(recorder_model_model)
def get(self, id):
"""Fetch a recorder model given its identifier"""
recorder_model = RecorderModel.query.get(id)
if recorder_model is not None:
return recorder_model
api_recorder.abort(404)
recorder_model_parser = api_recorder.parser()
recorder_model_parser.add_argument('notes', type=str, required=True)
@jwt_required
@api_recorder.doc('update_recorder_model')
@api_recorder.expect(recorder_model_parser)
@api_recorder.marshal_with(recorder_model_model)
def put(self, id):
"""Update a recorder_model given its identifier"""
num_rows_matched = RecorderModel.query.filter_by(id=id).update(api_recorder.payload)
if num_rows_matched < 1:
api_recorder.abort(404)
db.session.commit()
return "ok"
@api_recorder.route('/model')
class RecorderModelList(Resource):
@jwt_required
@api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model_model)
def get(self):
return RecorderModel.get_all()
@api_recorder.route('/command/<int:id>')
@api_recorder.response(404, 'Recorder Command not found')
@api_recorder.param('id', 'The recorder command identifier')
class RecorderCommandResource(Resource):
@jwt_required
@api_recorder.doc('get_recorder_command')
@api_recorder.marshal_with(recorder_command_model)
def get(self, id):
"""Fetch a recorder command given its identifier"""
recorder_command = RecorderCommand.query.get(id)
if recorder_command is not None:
return recorder_command
api_recorder.abort(404)
recorder_command_model_parser = api_recorder.parser()
recorder_command_model_parser.add_argument('description', type=str, required=False)
recorder_command_model_parser.add_argument('alternative_name', type=str, required=False)
@jwt_required
@api_recorder.doc('update_recorder_command')
@api_recorder.expect(recorder_command_model_parser)
@api_recorder.marshal_with(recorder_command_model)
def put(self, id):
"""Update a recorder command given its identifier"""
num_rows_matched = RecorderCommand.query.filter_by(id=id).update(api_recorder.payload)
if num_rows_matched < 1:
api_recorder.abort(404)
db.session.commit()
return "ok"
@api_recorder.route('/command')
class RecorderCommandList(Resource):
@jwt_required
@api_recorder.doc('recorder_commands')
@api_recorder.marshal_list_with(recorder_command_model)
def get(self):
"""
List all recorders commands
:return: recorder commands
"""
return RecorderCommand.get_all()

122
backend/api/room_api.py Normal file
View File

@@ -0,0 +1,122 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
from flask_jwt_extended import jwt_required
from flask_restplus import fields, Resource
from sqlalchemy import exc
from backend import db, app
from backend.api import api_room
from backend.models.room_model import Room
from backend.models.recorder_model import Recorder
room_model = api_room.model('Room', {
'id': fields.String(required=False, description='The room\'s identifier'),
'created_at': fields.DateTime(required=False, description='Creation date of the room info'),
'name': fields.String(required=True, description='The room\'s name'),
'alternate_name': fields.String(required=False, description='The room\'s alternate name'),
'comment': fields.String(required=False, description='The room\'s comment'),
'number': fields.String(required=True, description='The room\'s number'),
'building_name': fields.String(required=False, description='The building\'s name'),
'building_number': fields.String(required=False, description='The building\'s number'),
'recorder': fields.Nested(api_room.model('room_recorder',
{'id': fields.Integer(), 'name': fields.String(),
'ip': fields.String(), 'network_name': fields.String()}),
allow_null=True,
skip_none=False,
required=False,
description='Room recorder.'),
})
@api_room.route('/<int:id>')
@api_room.response(404, 'Room not found')
@api_room.param('id', 'The room identifier')
class RoomResource(Resource):
@jwt_required
@api_room.doc('get_room')
@api_room.marshal_with(room_model, skip_none=False)
def get(self, id):
"""Fetch a user given its identifier"""
room = Room.query.get(id)
if room is not None:
return room
api_room.abort(404)
@jwt_required
@api_room.doc('delete_todo')
@api_room.response(204, 'Todo deleted')
def delete(self, id):
'''Delete a task given its identifier'''
room = Room.query.get(id)
if room is not None:
db.session.delete(room)
db.session.commit()
return '', 204
api_room.abort(404)
@jwt_required
@api_room.doc('update_room')
@api_room.expect(room_model)
def put(self, id):
app.logger.debug(api_room.payload)
'''Update a task given its identifier'''
if "recorder_id" in api_room.payload:
if api_room.payload["recorder_id"] is None:
api_room.payload["recorder"] = None
else:
recorder = Recorder.query.get(api_room.payload["recorder_id"])
if recorder is not None:
api_room.payload["recorder"] = recorder
else:
return "specified recorder (id: {}) does not exist!".format(api_room.payload["recorder_id"]), 404
room = Room.query.get(id)
if room is not None:
room.recorder = api_room.payload["recorder"]
else:
num_rows_matched = Room.query.filter_by(id=id).update(api_room.payload)
db.session.commit()
return "ok"
api_room.abort(404)
@api_room.route('')
class RoomList(Resource):
@jwt_required
@api_room.doc('rooms')
@api_room.marshal_list_with(room_model, skip_none=False)
def get(self):
"""
List all rooms
:return: rooms
"""
return Room.get_all()
@jwt_required
@api_room.doc('create_room')
@api_room.expect(room_model)
@api_room.marshal_with(room_model, skip_none=False, code=201)
def post(self):
if "recorder_id" in api_room.payload:
if api_room.payload["recorder_id"] is None:
api_room.payload["recorder"] = None
else:
recorder = Recorder.query.get(api_room.payload["recorder_id"])
if recorder is not None:
api_room.payload["recorder"] = recorder
else:
return "specified recorder (id: {}) does not exist!".format(api_room.payload["recorder_id"]), 404
del api_room.payload["recorder_id"]
room = Room(**api_room.payload)
db.session.add(room)
try:
db.session.commit()
return room
except exc.IntegrityError as e:
db.session.rollback()
return str(e.detail), 400

107
backend/api/user_api.py Normal file
View File

@@ -0,0 +1,107 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
from datetime import datetime
from pprint import pprint
from flask_jwt_extended import get_jwt_identity, jwt_required, current_user
from flask_restplus import Resource, fields, inputs
from backend import db, app, jwt_auth
from backend.api import api_user
from backend.models.user_model import User, Group
user_model = api_user.model('User', {
'id': fields.String(required=True, description='The user\'s identifier'),
'first_name': fields.String(required=True, description='The user\'s first name'),
'last_name': fields.String(required=True, description='The user\'s last name'),
'email': fields.String(required=True, description='The user\'s email address'),
'nickname': fields.String(required=False, description='The user\'s nick name'),
'groups': fields.List(
fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})),
required=False, description='Group memberships.'),
})
user_update_parser = api_user.parser()
user_update_parser.add_argument('email', type=inputs.email, required=False, nullable=False, store_missing=False)
user_update_parser.add_argument('nickname', type=str, required=False, store_missing=False)
user_update_parser.add_argument('first_name', type=str, required=False, store_missing=False)
user_update_parser.add_argument('last_name', type=str, required=False, store_missing=False)
@api_user.route('/profile')
class Profile(Resource):
@jwt_required
@api_user.marshal_with(user_model)
def get(self):
"""Get infos about logged in user."""
current_user_id = get_jwt_identity()
app.logger.info(current_user_id)
return User.get_by_identifier(current_user_id)
@jwt_required
@api_user.expect(user_update_parser)
def put(self):
args = user_update_parser.parse_args()
args['last_time_modified'] = datetime.utcnow()
pprint(args)
print(current_user)
num_rows_matched = User.query.filter_by(id=current_user.id).update(args)
print(num_rows_matched)
if num_rows_matched < 1:
api_user.abort("Nothing has been updated!")
db.session.commit()
return "ok"
@api_user.route('')
class UserList(Resource):
"""
This is a test class.
"""
# @jwt_auth.login_required
@jwt_required
@api_user.doc('users')
@api_user.marshal_list_with(user_model)
def get(self):
"""
just a test!
:return: Hello: World
"""
current_user = get_jwt_identity()
app.logger.info(current_user)
return User.get_all()
@jwt_required
@api_user.doc('create_group')
@api_user.expect(user_model)
@api_user.marshal_with(user_model, code=201)
def post(self):
user = User(**api_user.payload)
db.session.add(user)
db.session.commit()
return user
@api_user.route('/<id>')
@api_user.param('id', 'The user identifier')
@api_user.response(404, 'User not found')
class UserResource(Resource):
@jwt_auth.login_required
@api_user.doc('get_user')
@api_user.marshal_with(user_model)
def get(self, id):
"""Fetch a user given its identifier"""
user = User.get_by_id(id)
if user is not None:
return user
api_user.abort(404)
# api_user.add_resource(UserResource, '/')

View File

@@ -0,0 +1,134 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import inspect
import pkgutil
from pprint import pprint
from flask_jwt_extended import jwt_required
from flask_restplus import fields, Resource
from backend import db, app
from backend.api import api_virtual_command
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room
import backend.recorder_adapters as r_a
virtual_command_model = api_virtual_command.model('VirtualCommand', {
'id': fields.String(required=False, description='The recorder\'s identifier'),
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
'name': fields.String(min_length=3, required=True, description='The recorder\'s name'),
'description': fields.String(required=False, description='The recorder\'s description'),
'parent_virtual_command': fields.Nested(api_virtual_command.model('VirtualCommandParent',
{
'id': fields.String(required=False,
description='The recorder\'s identifier'),
'name': fields.String(min_length=3,
required=True,
description='The recorder\'s name'),
},
required=False,
allow_null=True,
skip_none=False,
description='Parent virtual command.')),
'room': fields.Nested(api_virtual_command.model('recorder_room',
{'id': fields.Integer(), 'name': fields.String(),
'number': fields.String(), 'alternate_name': fields.String()}),
r0equired=False,
allow_null=True,
skip_none=False,
description='Room in which the recorder is located.')
})
# ==
@api_virtual_command.route('/<int:id>')
@api_virtual_command.response(404, 'Recorder not found')
@api_virtual_command.param('id', 'The recorder identifier')
class VirtualCommandResource(Resource):
@jwt_required
@api_virtual_command.doc('get_recorder')
@api_virtual_command.marshal_with(virtual_command_model, skip_none=False)
def get(self, id):
"""Fetch a recorder given its identifier"""
recorder = Recorder.query.get(id)
if recorder is not None:
return recorder
api_virtual_command.abort(404)
@jwt_required
@api_virtual_command.doc('delete_todo')
@api_virtual_command.response(204, 'Todo deleted')
def delete(self, id):
"""Delete a recorder given its identifier"""
recorder = Recorder.query.get(id)
if recorder is not None:
db.session.delete(recorder)
db.session.commit()
return '', 204
api_virtual_command.abort(404)
virtual_command_model_parser = api_virtual_command.parser()
virtual_command_model_parser.add_argument('notes', type=str, required=True)
@jwt_required
@api_virtual_command.doc('update_recorder')
@api_virtual_command.expect(virtual_command_model_parser)
def put(self, id):
"""Update a recorder given its identifier"""
num_rows_matched = Recorder.query.filter_by(id=id).update(api_virtual_command.payload)
if num_rows_matched < 1:
api_virtual_command.abort(404)
db.session.commit()
return "ok"
@api_virtual_command.route('')
class RecorderList(Resource):
@jwt_required
@api_virtual_command.doc('recorders')
@api_virtual_command.marshal_list_with(virtual_command_model, skip_none=False)
def get(self):
"""
List all recorders
:return: recorders
"""
return Recorder.get_all()
virtual_command_model_parser = api_virtual_command.parser()
virtual_command_model_parser.add_argument('notes', type=str, required=True)
@jwt_required
@api_virtual_command.doc('create_recorder')
@api_virtual_command.expect(virtual_command_model_parser)
@api_virtual_command.marshal_with(virtual_command_model, skip_none=False, code=201)
def post(self):
if "room_id" in api_virtual_command.payload:
if api_virtual_command.payload["room_id"] is None:
api_virtual_command.payload["room"] = None
else:
room = Room.query.get(api_virtual_command.payload["room_id"])
if room is not None:
api_virtual_command.payload["room"] = room
else:
return "specified room (id: {}) does not exist!".format(api_virtual_command.payload["room_id"]), 404
if "recorder_model_id" in api_virtual_command.payload:
if api_virtual_command.payload["recorder_model_id"] is None:
api_virtual_command.payload["recorder_model"] = None
else:
rec_model = RecorderModel.query.get(api_virtual_command.payload["recorder_model_id"])
if rec_model is not None:
api_virtual_command.payload["recorder_model"] = rec_model
else:
return "specified recorder model (id: {}) does not exist!".format(
api_virtual_command.payload["recorder_model_id"]), 404
recorder = Recorder(**api_virtual_command.payload)
db.session.add(recorder)
db.session.commit()
return recorder