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,52 +118,60 @@ 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") appointment = self.inspector.inspect(request_data.get("appointment"))
)
if rcode: if type(appointment) == Appointment:
rcode = HTTP_BAD_REQUEST # An appointment will fill 1 slot per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
error = "appointment rejected. Error {}: {}".format(rcode, message) required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
return jsonify({"error": error}), rcode
else: # Temporarily taking out slots to avoid abusing this via race conditions
user_pk = message # DISCUSS: It may be worth using signals here to avoid race conditions anyway
try:
self.gatekeeper.fill_slots(user_pk, required_slots)
appointment = self.inspector.inspect(request_data.get("appointment")) appointment_added, signature = self.watcher.add_appointment(appointment)
if type(appointment) == Appointment: if appointment_added:
# An appointment will fill 1 slot per ENCRYPTED_BLOB_MAX_SIZE_HEX block. rcode = HTTP_OK
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX) response = {"locator": appointment.locator, "signature": signature}
if self.gatekeeper.get_slots(user_pk) >= required_slots: else:
appointment_added, signature = self.watcher.add_appointment(appointment) # Adding back the slots since they were not used
self.gatekeeper.free_slots(user_pk, required_slots)
rcode = HTTP_SERVICE_UNAVAILABLE
error = "appointment rejected"
response = {"error": error}
if appointment_added: except NotEnoughSlots:
rcode = HTTP_OK # Adding back the slots since they were not used
response = {"locator": appointment.locator, "signature": signature} self.gatekeeper.free_slots(user_pk, required_slots)
self.gatekeeper.fill_subscription_slots(user_pk, required_slots) rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Error {}: {}".format(
else: errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS,
rcode = HTTP_SERVICE_UNAVAILABLE "Invalid signature or the user does not have enough slots available",
error = "appointment rejected" )
response = {"error": error} response = {"error": error}
else: elif type(appointment) == tuple:
rcode = errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS rcode = HTTP_BAD_REQUEST
error = "invalid signature or the user does not have enough slots available" error = "appointment rejected. Error {}: {}".format(appointment[0], appointment[1])
response = {"error": error} response = {"error": error}
elif type(appointment) == tuple: else:
rcode = HTTP_BAD_REQUEST # We should never end up here, since inspect only returns appointments or tuples. Just in case.
error = "appointment rejected. Error {}: {}".format(appointment[0], appointment[1]) rcode = HTTP_BAD_REQUEST
response = {"error": error} error = "appointment rejected. Request does not match the standard"
response = {"error": error}
else: else:
# We should never end up here, since inspect only returns appointments or tuples. Just in case.
rcode = HTTP_BAD_REQUEST rcode = HTTP_BAD_REQUEST
error = "appointment rejected. Request does not match the standard" 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}
else: else:

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