def post(cls): public_key = crypto_handler.pub_key() parms = request.get_json() print(f"Received register request: {parms}") if 'hwid' in parms and 'pkey' in parms: # Check if we have a safe with this id already this_safe = SafeModel.find_by_id(parms['hwid']) if this_safe: # We already know about this safe logging.info( f"SAFE: Rejected repeated safe registration hw_id: {parms['hwid']}" ) return {"error": msgs.SAFE_REGISTRATION_ERROR}, 400 else: now = datetime.utcnow() this_safe = SafeModel(hardware_id=parms['hwid'], public_key=parms['pkey'], last_update=now, unlock_time=now) this_safe.save_to_db() logging.info( f"SAFE: Registered new safe with hw_id: {parms['hwid']}") return {"key": public_key.decode("utf-8")}, 200 else: return {"error": msgs.SAFE_REGISTRATION_ERROR}, 400
def get(cls): """ :parameter """ this_user_id = get_jwt_identity() this_user = UserModel.find_by_id(this_user_id) relationship_list = RelationshipModel.find_any(this_user_id) safe_list = SafeModel.find_by_safeholder_id(this_user_id) summary_list = [] safe_ids = [] # First scan the safes in relationships for relationship in relationship_list: safe_ids.append(relationship.safe.hardware_id) summary_item = { 'hardware_id': relationship.safe.hardware_id, 'locked': all([ relationship.safe.hinge_closed, relationship.safe.lid_closed, relationship.safe.bolt_engaged ]), 'safeholder_displayname': relationship.safeholder.displayname, 'keyholder_displayname': relationship.keyholder.displayname } summary_list.append(summary_item) # then scan the safes not not in relationships for safe in safe_list: if safe.hardware_id not in safe_ids: summary_item = { 'hardware_id': safe.hardware_id, 'locked': all([ safe.hinge_closed, safe.lid_closed, safe.bolt_engaged ]), 'safeholder_displayname': this_user.displayname, 'keyholder_displayname': None } summary_list.append(summary_item) return { "safe_list": [safe_summary_schema.dump(item) for item in summary_list] }, 200
def delete(cls): """ Delete a relationship that already exists :return: """ parms = kh_claim_sh_schema.load(request.get_json()) safeholder = UserModel.find_by_displayname(parms['displayname']) keyholder = UserModel.find_by_id(get_jwt_identity()) now = datetime.now(timezone.utc) # First check if the safeholder exists if not safeholder: return { "msg": msgs.USER_NONEXISTANT.format(parms['displayname']) }, 400 # Now check if the safeholder has a safe - and its digital key matches safeholder_safes = SafeModel.find_by_safeholder_id(safeholder.id) if not safeholder_safes: # Safeholder has no safes return {"msg": msgs.INCORRECT_KEY}, 401 for safe in safeholder_safes: if safe.digital_key == parms['digital_key']: # We have a hit - now check if the KH is the KH for that relationship relationship_list = RelationshipModel.find_by_safe_id( safe.hardware_id) for relationship in relationship_list: if (relationship.keyholder_id == keyholder.id) and ( relationship.end_date is None): # OK, we have an active relationship between this KH and the SH # Set a new digital_key for the safe - and unlock it (for safety) digital_key = str(uuid4()) safe.digital_key = digital_key safe.auth_to_unlock = expression.true() safe.unlock_time = now safe.last_update = now safe.save_to_db() # Then terminate the relationship - and send a mail to the safeholder relationship.end_date = date.today() relationship.send_relationship_email(status='end') relationship.save_to_db() # Log the end of the relationship logging.info( f"RELATIONSHIP: END {relationship.keyholder.username} - " f"{relationship.safeholder.username}") return {"msg": msgs.RELATIONSHIP_TERMINATED}, 200 return {"msg": msgs.INCORRECT_KH}, 401 return {"msg": msgs.INCORRECT_KEY}, 401
def post(cls): """ For a KH to claim a SH they must pass the SH displayname AND a correct digital key for that user's safe :param """ parms = kh_claim_sh_schema.load(request.get_json()) safeholder = UserModel.find_by_displayname(parms['displayname']) potential_kh = UserModel.find_by_id(get_jwt_identity()) # First check if the safeholder exists if not safeholder: return { "msg": msgs.USER_NONEXISTANT.format(parms['displayname']) }, 400 # Then check if potential KH is trying to claim themself as a SH if safeholder.id == potential_kh.id: return {"msg": msgs.KH_EQ_SH}, 401 # Now check if the safeholder has a safe - and its digital key matches safeholder_safes = SafeModel.find_by_safeholder_id(safeholder.id) if not safeholder_safes: # Safeholder has no safes return {"msg": msgs.INCORRECT_KEY}, 401 for safe in safeholder_safes: if safe.digital_key == parms['digital_key']: # We have a hit - now check if the SH is not already in a relationship for that safe relationship_list = RelationshipModel.find_by_safe_id( safe.hardware_id) for relationship in relationship_list: if (relationship.safeholder_id == safeholder.id) and ( relationship.end_date is None): return {"msg": msgs.SH_IN_RELATIONSHIP}, 401 # OK now we can set up the relationship relationship = RelationshipModel(keyholder_id=potential_kh.id, safeholder_id=safeholder.id, safe_id=safe.hardware_id, start_date=date.today()) relationship.save_to_db() # Send email to Safeholder to confirm start of relationship relationship.send_relationship_email(status='start') # Log the start of the relationship logging.info( f"RELATIONSHIP: START {relationship.keyholder.username} - " f"{relationship.safeholder.username}") return {"msg": msgs.RELATIONSHIP_ESTABLISHED}, 200 # If we get here the safeholder had no safes that the digital key fitted return {"msg": msgs.INCORRECT_KEY}, 401
def post(cls): """ Assign current logged on user as safeholder for the specified safe - assuming it is available Generate, save and return a digital key for the Safeholder to pass to their chosen Keyholder """ parms = safe_claim_schema.load(request.get_json()) this_user = UserModel.find_by_id(get_jwt_identity()) # Check if we have this safe requested_safe = SafeModel.find_by_id(parms['hardware_id']) if not requested_safe: return {"error": msgs.CLAIM_NO_SAFE}, 400 # Then check if the safe is already claimed if requested_safe.safeholder: return {"error": msgs.CLAIM_NOT_AVAILABLE}, 400 # Allocate this user as safeholder - generate the digital key first requested_safe.safeholder = this_user digital_key = str(uuid4()) requested_safe.digital_key = digital_key requested_safe.save_to_db() return {"digital_key": digital_key}, 200
def delete(cls): """ Remove the current logged-on user as the holder of this safe - make it available for re-claiming """ parms = safe_claim_schema.load(request.get_json()) this_user = UserModel.find_by_id(get_jwt_identity()) # Check if we have this safe requested_safe = SafeModel.find_by_id(parms['hardware_id']) if not requested_safe: return {"error": msgs.CLAIM_NO_SAFE}, 400 # Next check if the safe is owned by this_user if requested_safe.safeholder is None: # The safe does not have an owner - cannot delete it return {"error": msgs.RELEASE_NOT_OWNED}, 400 if requested_safe.safeholder.id != this_user.id: # The safe does not belong to you return {"error": msgs.RELEASE_NOT_OWNED}, 400 # TODO - Need to also check if the safeholder is in a relationship - should not permit release if so # OK, we can remove the safeholder requested_safe.safeholder = None requested_safe.save_to_db() return {"msg": "ok"}, 200
def post(cls): valid_request = False now = datetime.utcnow() parms = request.get_json() if all(map(lambda x: x in parms, ['hwid', 'sig', 'msg'])): # Check if we have a safe with this ID this_safe = SafeModel.find_by_id(parms['hwid']) if this_safe: # Check message is valid msg_valid, message = crypto_handler.decrypt( msg=parms['msg'], sig=parms['sig'], pkey=this_safe.public_key) if msg_valid: # Interpret content and update database # Remember, message may be multiple lines print(f"Message received = {message}") if '\n' in message: message_lines = message.split('\n') else: message_lines = [ message, ] status_parts = message_lines[0].split(',') if len(status_parts) == 6: this_safe.last_update = convert_timestamp( status_parts[2]) if status_parts[3] == 'True': this_safe.hinge_closed = True if status_parts[4] == 'True': this_safe.lid_closed = True if status_parts[5] == 'True': this_safe.bolt_engaged = True # Now check if there are any time-based updates to make if this_safe.unlock_time < now: this_safe.auth_to_unlock = True this_safe.save_to_db() if len(message_lines) > 1: # Add entries to Safe events database for i in range(len(message_lines) - 1): if message_lines[i + 1].startswith('EVENT'): event_parts = message_lines[i + 1].split(',') if len(event_parts) == 3: event = SafeEventModel( hardware_id=this_safe.hardware_id, event_code=event_codes.get( event_parts[2], DEFAULT_EVENT), detail=event_parts[2], timestamp=convert_timestamp( event_parts[1])) event.save_to_db() logging.info( f"Safe parameters updated for {this_safe.hardware_id}" ) # Now construct a response, encrypt, sign and return it server_message_base = 'Auth_to_unlock:{}:{}\nUnlock_time:{}\nSettings:SCANFREQ={' \ '}:REPORTFREQ={}:PROXIMITYUNIT={}:DISPLAYPROXIMITY={}' if this_safe.auth_to_unlock: auth_msg = 'TRUE' else: auth_msg = 'FALSE' if this_safe.display_proximity: disp_msg = 'TRUE' else: disp_msg = 'FALSE' server_message = server_message_base.format( auth_msg, now, this_safe.unlock_time, this_safe.scan_freq, this_safe.report_freq, this_safe.proximity_unit, disp_msg) msg_enc_64, msg_sig_64 = crypto_handler.encrypt( msg=server_message, safe_pkey=this_safe.public_key) return { "msg": msg_enc_64.decode('utf-8'), "sig": msg_sig_64.decode('utf-8') }, 200 else: logging.info( f"Invalid checking message received from {parms['hwid']}" ) return {"error": msgs.SAFE_CHECKIN_ERROR}, 400 else: return {"error": msgs.SAFE_CHECKIN_ERROR}, 400 logging.info(f"Improperly formed checkin request: {parms}") return {"error": msgs.SAFE_CHECKIN_ERROR}, 400
def get(clscls): return { "safes": [safe_schema.dump(safe) for safe in SafeModel.find_available()] }