def test_get_appointment_subscription_expired(internal_api, stub, generate_dummy_appointment): # UNAUTHENTICATED is returned if the subscription has expired stub.register(RegisterRequest(user_id=user_id)) # Send the appointment first appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) send_appointment(stub, appointment, appointment_signature) # Modify the user data so the subscription has already ended expiry = internal_api.watcher.block_processor.get_block_count( ) - internal_api.watcher.gatekeeper.expiry_delta - 1 internal_api.watcher.gatekeeper.registered_users[ user_id].subscription_expiry = expiry # Request it back message = f"get appointment {appointment.locator}" request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) with pytest.raises(grpc.RpcError) as e: stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED assert "Your subscription expired at" in e.value.details()
def test_get_appointment_non_registered(internal_api, stub, generate_dummy_appointment): # If the user is not registered or the appointment does not belong to him the response should be NOT_FOUND stub.register(RegisterRequest(user_id=user_id)) another_user_sk, another_user_pk = generate_keypair() # Send the appointment first appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) send_appointment(stub, appointment, appointment_signature) # Request it back message = f"get appointment {appointment.locator}" request_signature = Cryptographer.sign(message.encode("utf-8"), another_user_sk) with pytest.raises(grpc.RpcError) as e: stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert "Appointment not found" in e.value.details() # Notice how the request will succeed if `user` (user_id) requests it request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) response = stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert isinstance(response, GetAppointmentResponse)
def test_register_bitcoind_crash(internal_api, stub, monkeypatch): monkeypatch.setattr(internal_api.watcher, "register", mock_connection_refused_return) with pytest.raises(grpc.RpcError) as e: stub.register(RegisterRequest(user_id=user_id)) assert e.value.code() == grpc.StatusCode.UNAVAILABLE assert "Service unavailable" in e.value.details()
def test_register_wrong_user_id(internal_api, stub): # If the user id is wrong we should get INVALID_ARGUMENT with the proper message wrong_user_id = get_random_value_hex(32) with pytest.raises(grpc.RpcError) as e: stub.register(RegisterRequest(user_id=wrong_user_id)) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert "Provided public key does not match expected format" in e.value.details( )
def test_get_subscription_info(internal_api, stub): stub.register(RegisterRequest(user_id=user_id)) # Request subscription details message = "get subscription info" request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) response = stub.get_subscription_info( GetSubscriptionInfoRequest(signature=request_signature)) assert isinstance(response, GetUserResponse)
def test_add_appointment(internal_api, stub, generate_dummy_appointment): # Normal request should work just fine (user needs to be registered) stub.register(RegisterRequest(user_id=user_id)) appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) response = send_appointment(stub, appointment, appointment_signature) assert isinstance(response, AddAppointmentResponse)
def test_register(internal_api, stub, monkeypatch): # Normal request should work just fine # Monkeypatch the response from the Watcher slots = 100 expiry = 1000 sig = get_random_value_hex(73) monkeypatch.setattr(internal_api.watcher, "register", lambda x: (slots, expiry, sig)) response = stub.register(RegisterRequest(user_id=user_id)) assert isinstance(response, RegisterResponse)
def test_register_wrong_user_id(internal_api, stub, monkeypatch): # If the user id is wrong we should get INVALID_ARGUMENT with the proper message wrong_user_id = get_random_value_hex(32) # Monkeypatch the response from the Watcher monkeypatch.setattr(internal_api.watcher, "register", raise_invalid_parameter) with pytest.raises(grpc.RpcError) as e: stub.register(RegisterRequest(user_id=wrong_user_id)) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert "Provided public key does not match expected format" in e.value.details( )
def test_get_appointment_non_existent(internal_api, stub): # Non-existing appointment will also return NOT_FOUND stub.register(RegisterRequest(user_id=user_id)) # Request it back locator = get_random_value_hex(16) message = f"get appointment {locator}" request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) with pytest.raises(grpc.RpcError) as e: stub.get_appointment( GetAppointmentRequest(locator=locator, signature=request_signature)) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert "Appointment not found" in e.value.details()
def test_add_appointment_limit_reached(internal_api, stub, generate_dummy_appointment, monkeypatch): # If the tower appointment limit is reached RESOURCE_EXHAUSTED should be returned monkeypatch.setattr(internal_api.watcher, "max_appointments", 0) stub.register(RegisterRequest(user_id=user_id)) appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) e = send_wrong_appointment(stub, appointment, appointment_signature) assert e.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED assert "Appointment limit reached" in e.value.details()
def test_add_appointment_already_triggered(internal_api, stub, generate_dummy_appointment): # If the appointment has already been trigger we should get ALREADY_EXISTS stub.register(RegisterRequest(user_id=user_id)) appointment, _ = generate_dummy_appointment() appointment_uuid = hash_160("{}{}".format(appointment.locator, user_id)) # Adding the uuid to the Responder trackers so the Watcher thinks it is in there. The data does not actually matters internal_api.watcher.responder.trackers[appointment_uuid] = {} appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) e = send_wrong_appointment(stub, appointment, appointment_signature) assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS assert "The provided appointment has already been triggered" in e.value.details( )
def test_add_appointment_subscription_expired(internal_api, stub, generate_dummy_appointment): # UNAUTHENTICATED is returned if the subscription has expired # Register the user and set the expiry to the current block stub.register(RegisterRequest(user_id=user_id)) internal_api.watcher.gatekeeper.registered_users[ user_id].subscription_expiry = internal_api.watcher.block_processor.get_block_count( ) appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) e = send_wrong_appointment(stub, appointment, appointment_signature) assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED assert "Your subscription expired at" in e.value.details()
def test_add_appointment_not_enough_slots(internal_api, stub, generate_dummy_appointment): # UNAUTHENTICATED should also be get if the user does not have enough appointment slots # Register the user and set the slots to 0 stub.register(RegisterRequest(user_id=user_id)) internal_api.watcher.gatekeeper.registered_users[ user_id].available_slots = 0 appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) e = send_wrong_appointment(stub, appointment, appointment_signature) assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED assert "Invalid signature or user does not have enough slots available" in e.value.details( )
def test_get_appointment(internal_api, stub, generate_dummy_appointment): # Requests should work provided the user is registered and the appointment exists for him stub.register(RegisterRequest(user_id=user_id)) # Send the appointment first appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) send_appointment(stub, appointment, appointment_signature) # Request it back message = f"get appointment {appointment.locator}" request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) response = stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert isinstance(response, GetAppointmentResponse)
def test_get_subscription_info_expired(internal_api, stub): stub.register(RegisterRequest(user_id=user_id)) # Modify the user data so the subscription has already ended expiry = internal_api.watcher.block_processor.get_block_count( ) - internal_api.watcher.gatekeeper.expiry_delta - 1 internal_api.watcher.gatekeeper.registered_users[ user_id].subscription_expiry = expiry # Request subscription details message = "get subscription info" request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) with pytest.raises(grpc.RpcError) as e: stub.get_subscription_info( GetSubscriptionInfoRequest(signature=request_signature)) assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED assert "Your subscription expired at" in e.value.details()
def test_register(internal_api, stub): # Normal request should work just fine response = stub.register(RegisterRequest(user_id=user_id)) assert isinstance(response, RegisterResponse)
def register(self): """ Registers a user by creating a subscription. Registration is pretty straightforward for now, since it does not require payments. The amount of slots and expiry of the subscription cannot be requested by the user yet either. This is linked to the previous point. Users register by sending a public key to the proper endpoint. This is exploitable atm, but will be solved when payments are introduced. Returns: :obj:`tuple`: A tuple containing the response (:obj:`str`) and response code (:obj:`int`). For accepted requests, the ``rcode`` is always 200 and the response contains a json with the public key and number of slots in the subscription. For rejected requests, the ``rcode`` is a 404 and the value contains an application error, and an error message. Error messages can be found at ``common.errors``. """ remote_addr = get_remote_addr() self.logger.info("Received register request", from_addr="{}".format(remote_addr)) # Check that data type and content are correct. Abort otherwise. try: request_data = get_request_data_json(request) except InvalidParameter as e: self.logger.info("Received invalid register request", from_addr="{}".format(remote_addr)) return jsonify({ "error": str(e), "error_code": errors.INVALID_REQUEST_FORMAT }), HTTP_BAD_REQUEST user_id = request_data.get("public_key") if user_id: try: r = self.stub.register(RegisterRequest(user_id=user_id)) rcode = HTTP_OK response = json_format.MessageToDict( r, including_default_value_fields=True, preserving_proto_field_name=True) response["public_key"] = user_id except grpc.RpcError as e: rcode = HTTP_BAD_REQUEST response = { "error": e.details(), "error_code": errors.REGISTRATION_MISSING_FIELD } else: rcode = HTTP_BAD_REQUEST response = { "error": "public_key not found in register message", "error_code": errors.REGISTRATION_WRONG_FIELD_FORMAT, } self.logger.info("Sending response and disconnecting", from_addr="{}".format(remote_addr), response=response) return jsonify(response), rcode