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)
Beispiel #17
0
    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