Improves and simplifies add_appointment

This commit is contained in:
Sergi Delgado Segura
2020-03-25 21:10:58 +01:00
parent 83b3913cb5
commit f7260bc1ce
2 changed files with 65 additions and 63 deletions

View File

@@ -4,6 +4,7 @@ from math import ceil
from flask import Flask, request, abort, jsonify from flask import Flask, request, abort, jsonify
import teos.errors as errors import teos.errors as errors
from teos.gatekeeper import NotEnoughSlots
from teos import HOST, PORT, LOG_PREFIX from teos import HOST, PORT, LOG_PREFIX
from common.logger import Logger from common.logger import Logger
@@ -117,41 +118,41 @@ class API:
if request.is_json: if request.is_json:
# Check content type once if properly defined # Check content type once if properly defined
request_data = request.get_json() request_data = request.get_json()
user_pk = self.gatekeeper.identify_user(request_data.get("appointment"), request_data.get("signature"))
rcode, message = self.gatekeeper.identify_user( if user_pk:
request_data.get("appointment"), request_data.get("signature")
)
if rcode:
rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Error {}: {}".format(rcode, message)
return jsonify({"error": error}), rcode
else:
user_pk = message
appointment = self.inspector.inspect(request_data.get("appointment")) appointment = self.inspector.inspect(request_data.get("appointment"))
if type(appointment) == Appointment: if type(appointment) == Appointment:
# An appointment will fill 1 slot per ENCRYPTED_BLOB_MAX_SIZE_HEX block. # An appointment will fill 1 slot per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX) required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
if self.gatekeeper.get_slots(user_pk) >= required_slots: # Temporarily taking out slots to avoid abusing this via race conditions
# DISCUSS: It may be worth using signals here to avoid race conditions anyway
try:
self.gatekeeper.fill_slots(user_pk, required_slots)
appointment_added, signature = self.watcher.add_appointment(appointment) appointment_added, signature = self.watcher.add_appointment(appointment)
if appointment_added: if appointment_added:
rcode = HTTP_OK rcode = HTTP_OK
response = {"locator": appointment.locator, "signature": signature} response = {"locator": appointment.locator, "signature": signature}
self.gatekeeper.fill_subscription_slots(user_pk, required_slots)
else: else:
# Adding back the slots since they were not used
self.gatekeeper.free_slots(user_pk, required_slots)
rcode = HTTP_SERVICE_UNAVAILABLE rcode = HTTP_SERVICE_UNAVAILABLE
error = "appointment rejected" error = "appointment rejected"
response = {"error": error} response = {"error": error}
else: except NotEnoughSlots:
rcode = errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS # Adding back the slots since they were not used
error = "invalid signature or the user does not have enough slots available" self.gatekeeper.free_slots(user_pk, required_slots)
rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Error {}: {}".format(
errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS,
"Invalid signature or the user does not have enough slots available",
)
response = {"error": error} response = {"error": error}
elif type(appointment) == tuple: elif type(appointment) == tuple:
@@ -165,6 +166,14 @@ class API:
error = "appointment rejected. Request does not match the standard" error = "appointment rejected. Request does not match the standard"
response = {"error": error} response = {"error": error}
else:
rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Error {}: {}".format(
errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS,
"Invalid signature or the user does not have enough slots available",
)
response = {"error": error}
else: else:
rcode = HTTP_BAD_REQUEST rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Request is not json encoded" error = "appointment rejected. Request is not json encoded"

View File

@@ -1,13 +1,18 @@
import re import re
import teos.errors as errors
from common.appointment import Appointment from common.appointment import Appointment
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
SUBSCRIPTION_SLOTS = 1 SUBSCRIPTION_SLOTS = 1
# TODO: UNITTEST, DOCS # TODO: UNITTEST, DOCS
class NotEnoughSlots(ValueError):
"""Raise this when trying to subtract more slots than a user has available"""
pass
class Gatekeeper: class Gatekeeper:
def __init__(self): def __init__(self):
self.registered_users = {} self.registered_users = {}
@@ -36,15 +41,6 @@ class Gatekeeper:
return self.registered_users[user_pk] return self.registered_users[user_pk]
def fill_subscription_slots(self, user_pk, n):
slots = self.registered_users.get(user_pk)
# FIXME: This looks pretty dangerous. I'm guessing race conditions can happen here.
if slots == n:
self.registered_users.pop(user_pk)
else:
self.registered_users[user_pk] -= n
def identify_user(self, appointment_data, signature): def identify_user(self, appointment_data, signature):
""" """
Checks if the provided user signature is comes from a registered user with available appointment slots. Checks if the provided user signature is comes from a registered user with available appointment slots.
@@ -54,33 +50,21 @@ class Gatekeeper:
signature (:obj:`str`): the user's signature (hex encoded). signature (:obj:`str`): the user's signature (hex encoded).
Returns: Returns:
:obj:`tuple`: A tuple (return code, message) as follows: :obj:`str` or `None`: a compressed key if it can be recovered from the signature and matches a registered
user. ``None`` otherwise.
- ``(0, None)`` if the user can be identified (recovered pk belongs to a registered user) and the user has
available slots.
- ``!= (0, None)`` otherwise.
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD`` and ``APPOINTMENT_INVALID_SIGNATURE``.
""" """
if signature is None: user_pk = None
rcode = errors.APPOINTMENT_EMPTY_FIELD
message = "empty signature received"
else: if signature is not None:
appointment = Appointment.from_dict(appointment_data) appointment = Appointment.from_dict(appointment_data)
rpk = Cryptographer.recover_pk(appointment.serialize(), signature) rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
compressed_user_pk = Cryptographer.get_compressed_pk(rpk) compressed_pk = Cryptographer.get_compressed_pk(rpk)
if compressed_user_pk and compressed_user_pk in self.registered_users: if compressed_pk in self.registered_users and self.registered_users.get(compressed_pk) > 0:
rcode = 0 user_pk = compressed_pk
message = compressed_user_pk
else: return user_pk
rcode = errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS
message = "invalid signature or the user does not have enough slots available"
return rcode, message
def get_slots(self, user_pk): def get_slots(self, user_pk):
""" """
@@ -95,3 +79,12 @@ class Gatekeeper:
""" """
slots = self.registered_users.get(user_pk) slots = self.registered_users.get(user_pk)
return slots if slots is not None else 0 return slots if slots is not None else 0
def fill_slots(self, user_pk, n):
if n >= self.registered_users.get(user_pk):
self.registered_users[user_pk] -= n
else:
raise NotEnoughSlots("No enough empty slots")
def free_slots(self, user_pk, n):
self.registered_users[user_pk] += n