From 197adf2170e9f55a35b3d30a524c08055e31b696 Mon Sep 17 00:00:00 2001 From: Renato Massaro Date: Fri, 10 Nov 2017 01:25:25 -0200 Subject: [PATCH 1/2] Support client-defined request_id --- lib/account/websocket/channel/account.ex | 18 ++- .../channel/account/requests/logout.ex | 30 +++++ lib/account/websocket/routes.ex | 22 ---- lib/endpoint.ex | 3 +- lib/event/event.ex | 35 +++++- lib/event/loggable/flow.ex | 12 +- lib/event/meta.ex | 16 ++- lib/log/event/handler/log.ex | 7 +- lib/process/action/top.ex | 38 +++--- lib/process/event/handler/top.ex | 2 +- lib/process/event/process.ex | 67 +++------- lib/process/executable.ex | 20 +-- lib/process/process.ex | 2 +- lib/software/action/flow/file.ex | 20 ++- lib/software/action/flow/file/transfer.ex | 52 ++++++-- lib/software/event/file.ex | 4 +- lib/software/event/handler/file/transfer.ex | 2 +- lib/software/public/file.ex | 26 ++-- lib/software/public/index.ex | 26 ++-- lib/software/public/pftp.ex | 10 +- lib/software/public/view/file.ex | 22 ---- .../websocket/requests/cracker/bruteforce.ex | 7 +- .../websocket/requests/file/download.ex | 12 +- .../websocket/requests/pftp/file/download.ex | 6 +- lib/universe/bank/action/flow/bank_account.ex | 8 +- .../bank/action/flow/bank_transfer.ex | 11 +- lib/websocket/request.ex | 49 +++++++- lib/websocket/requestable.ex | 11 +- lib/websocket/requests_channel.ex | 18 --- lib/websocket/utils.ex | 59 ++++----- lib/websocket/websocket.ex | 78 +++++++++--- .../channel/account/requests/logout_test.exs | 30 +++++ test/account/websocket/routes_test.exs | 33 ----- test/event/loggable/flow_test.exs | 6 +- test/event/notification_handler_test.exs | 58 +++++---- test/features/file/transfer_test.exs | 94 ++++++++++++++ test/features/hack_test.exs | 43 ++++--- test/features/process/lifecycle_test.exs | 17 +-- test/features/process/recalque_test.exs | 13 +- test/process/event/process_created_test.exs | 118 +++++++++++------- .../channel/server/requests/cracker_test.exs | 33 +++-- .../channel/server/requests/pftp_test.exs | 13 +- .../action/flow/file/transfer_test.exs | 26 ++-- test/software/action/flow/file_test.exs | 7 +- .../process/cracker/bruteforce_test.exs | 7 +- test/software/public/index_test.exs | 13 +- test/software/public/pftp_test.exs | 4 +- test/software/public/software_test.exs | 10 +- .../websocket/requests/file/download_test.exs | 7 +- test/support/channel/request/helper.ex | 3 +- test/support/channel/setup.ex | 25 +++- test/support/event/setup/process.ex | 17 +-- test/support/process/helper/top.ex | 13 +- test/support/software/flow.ex | 4 +- test/support/universe/bank/setup.ex | 2 +- .../bank/action/flow/bank_account_test.exs | 7 +- .../bank/action/flow/bank_transfer_test.exs | 6 +- .../bank/event/handler/bank/transfer_test.exs | 4 +- 58 files changed, 792 insertions(+), 514 deletions(-) create mode 100644 lib/account/websocket/channel/account/requests/logout.ex delete mode 100644 lib/account/websocket/routes.ex delete mode 100644 lib/software/public/view/file.ex delete mode 100644 lib/websocket/requests_channel.ex create mode 100644 test/account/websocket/channel/account/requests/logout_test.exs delete mode 100644 test/account/websocket/routes_test.exs create mode 100644 test/features/file/transfer_test.exs diff --git a/lib/account/websocket/channel/account.ex b/lib/account/websocket/channel/account.ex index 25566298..cfe6e8df 100644 --- a/lib/account/websocket/channel/account.ex +++ b/lib/account/websocket/channel/account.ex @@ -10,6 +10,8 @@ channel Helix.Account.Websocket.Channel.Account do as: BootstrapRequest alias Helix.Account.Websocket.Channel.Account.Requests.EmailReply, as: EmailReplyRequest + alias Helix.Account.Websocket.Channel.Account.Requests.Logout, + as: LogoutRequest join _, AccountJoin @@ -19,7 +21,7 @@ channel Helix.Account.Websocket.Channel.Account do Replies to a Storyline email. Params: - *reply_id: Reply identifier. + *reply_id: Reply identifier. Returns: %{} @@ -31,6 +33,20 @@ channel Helix.Account.Websocket.Channel.Account do """ topic "email.reply", EmailReplyRequest + @doc """ + Logs out from the channel. + + Params: nil + + Returns: nil + + **Channel will be closed** + + Errors: + - internal + """ + topic "account.logout", LogoutRequest + @doc """ Intercepts and handles outgoing events. """ diff --git a/lib/account/websocket/channel/account/requests/logout.ex b/lib/account/websocket/channel/account/requests/logout.ex new file mode 100644 index 00000000..2bfe72d4 --- /dev/null +++ b/lib/account/websocket/channel/account/requests/logout.ex @@ -0,0 +1,30 @@ +import Helix.Websocket.Request + +request Helix.Account.Websocket.Channel.Account.Requests.Logout do + @moduledoc """ + Invalidates the session token and shuts down the socket. + """ + + alias Helix.Websocket + alias Helix.Account.Websocket.Controller.Account, as: AccountController + + def check_params(request, _socket), + do: reply_ok(request) + + def check_permissions(request, _socket), + do: reply_ok(request) + + def handle_request(request, socket) do + + AccountController.logout(socket.assigns, %{}) + + socket + |> Websocket.id() + |> Helix.Endpoint.broadcast("disconnect", %{}) + + reply_ok(request) + end + + def reply(_request, _socket), + do: {:stop, :shutdown} +end diff --git a/lib/account/websocket/routes.ex b/lib/account/websocket/routes.ex deleted file mode 100644 index f3359de1..00000000 --- a/lib/account/websocket/routes.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule Helix.Account.Websocket.Routes do - - alias Helix.Websocket - alias Helix.Account.Websocket.Controller.Account, as: AccountController - - # Note that this is somewhat a hack to allow us to break our request-response - # channel into several parts (one on each domain). So this code will be - # executed inside the "requests" channel and thus must follow Phoenix - # Channel's callback interface: - # https://hexdocs.pm/phoenix/Phoenix.Channel.html#c:handle_in/3 - - def account_logout(socket) do - AccountController.logout(socket.assigns, %{}) - - socket_id = Websocket.id(socket) - Helix.Endpoint.broadcast(socket_id, "disconnect", %{}) - - # Logout will blacklist the token and stop the socket, so, this only makes - # sense - {:stop, :shutdown, socket} - end -end diff --git a/lib/endpoint.ex b/lib/endpoint.ex index 9be4aed6..234ef6d6 100644 --- a/lib/endpoint.ex +++ b/lib/endpoint.ex @@ -8,8 +8,7 @@ defmodule Helix.Endpoint do plug Corsica, origins: Application.get_env(:helix, Helix.Endpoint)[:allowed_cors], - allow_headers: ["content-type", "x-request-id"], - expose_headers: ["X-Request-Id"] + allow_headers: ["content-type", "x-request-id"] plug Plug.Static, at: "/", diff --git a/lib/event/event.ex b/lib/event/event.ex index bdff2aba..5052b0b1 100644 --- a/lib/event/event.ex +++ b/lib/event/event.ex @@ -8,12 +8,15 @@ defmodule Helix.Event do import HELL.Macros + alias Helix.Websocket.Request.Relay, as: RequestRelay + alias Helix.Process.Model.Process alias Helix.Event.Dispatcher, as: HelixDispatcher alias Helix.Event.Meta, as: EventMeta alias Helix.Event.State.Timer, as: EventTimer - alias Helix.Process.Model.Process @type t :: HELF.Event.t + @type source :: t | RequestRelay.t + @type relay :: source @doc """ Top-level macro for an event. @@ -58,6 +61,20 @@ defmodule Helix.Event do to: EventMeta end + @doc """ + This is pure syntactic sugar for: set_{field}(event, get_{field}(source)) + + I.e. we get {field} from `source` and assign it to `event`. + """ + defmacro relay(event, field, source) do + quote do + event = unquote(event) + source = unquote(source) + + unquote(:"set_#{field}")(event, unquote(:"get_#{field}")(source)) + end + end + @spec emit([t] | t, from: t) :: term @doc """ @@ -98,12 +115,21 @@ defmodule Helix.Event do def emit_after(event, interval), do: EventTimer.emit_after(event, interval) - @spec inherit(t, t) :: + @spec inherit(t, source) :: t docp """ The application wants to emit `event`, which is coming from `source`. On this case, `event` will inherit the source's metadata according to the logic below. + + Note that `source` may either be another event (`t`) or a request relay + (`RequestRelay.t`). If it's a RequestRelay, then this event is being emitted + as a result of a direct action from the player. On the other hand, if `source` + is an event, it means this event is a side-effect of another event. """ + defp inherit(event, nil), + do: event + defp inherit(event, relay = %RequestRelay{}), + do: set_request_id(event, relay.request_id) defp inherit(event, source) do # Relay the `process_id` event = @@ -116,9 +142,10 @@ defmodule Helix.Event do # Accumulate source event on the stacktrace, and save it on the next event stack = get_stack(source) || [] - new_stack = stack ++ [source.__struct__] + event = set_stack(event, stack ++ [source.__struct__]) - event = set_stack(event, new_stack) + # Relay the request_id information + event = relay(event, :request_id, source) # Everything has been inherited, we are ready to emit/1 the event. event diff --git a/lib/event/loggable/flow.ex b/lib/event/loggable/flow.ex index 242be3e3..a27a450d 100644 --- a/lib/event/loggable/flow.ex +++ b/lib/event/loggable/flow.ex @@ -138,14 +138,15 @@ defmodule Helix.Event.Loggable.Flow do do: {server_id, entity_id, msg} @spec save([log_entry] | log_entry) :: - term + [Event.t] @doc """ - Receives the list of generated entries, which is returned by each event - that implements the Loggable protocol, and inserts them into the game - database, emitting the relevant `LogCreatedEvent` + Receives the list of generated entries, which is returned by each event that + implements the Loggable protocol, and inserts them into the game database. + Accumulates the corresponding `LogCreatedEvent`s, which shall be emitted by + the caller. """ def save([]), - do: :ok + do: [] def save(log_entry = {_, _, _}), do: save([log_entry]) def save(logs) do @@ -155,6 +156,5 @@ defmodule Helix.Event.Loggable.Flow do events end) |> List.flatten() - |> Enum.each(&Event.emit/1) end end diff --git a/lib/event/meta.ex b/lib/event/meta.ex index a48aba0b..c2f7f39a 100644 --- a/lib/event/meta.ex +++ b/lib/event/meta.ex @@ -12,12 +12,15 @@ defmodule Helix.Event.Meta do @type t :: %{ event_id: HETypes.uuid | nil, - process_id: Process.id | nil + process_id: Process.id | nil, + stack: [Event.t] | nil, + request_id: binary | nil } @type rendered :: %{ event_id: String.t | nil, - process_id: String.t | nil + process_id: String.t | nil, + request_id: binary | nil } @meta_key :__meta__ @@ -36,7 +39,11 @@ defmodule Helix.Event.Meta do # The `stack` field is a rudimentary stacktrace. Every time an event is # emitted from another one, the previous event name is stored on this stack. - :stack + :stack, + + # The `request_id` field associates which request was responsible for this + # event. Subsequent events will carry on (relay) this request_id as well. + :request_id ] @doc """ @@ -59,7 +66,8 @@ defmodule Helix.Event.Meta do def render(event) do %{ event_id: get_event_id(event), - process_id: get_process_id(event) |> Utils.stringify() + process_id: get_process_id(event) |> Utils.stringify(), + request_id: get_request_id(event) } end diff --git a/lib/log/event/handler/log.ex b/lib/log/event/handler/log.ex index c5711063..f4a6897d 100644 --- a/lib/log/event/handler/log.ex +++ b/lib/log/event/handler/log.ex @@ -15,12 +15,15 @@ defmodule Helix.Log.Event.Handler.Log do Generic event handler for all Helix events. If the event implement the Loggable protocol, it will guide it through the LoggableFlow, making sure the relevant log entries are generated and saved + + Emits `LogCreatedEvent` """ def handle_event(event) do if Loggable.impl_for(event) do event |> Loggable.generate() |> Loggable.Flow.save() + |> Event.emit(from: event) end end @@ -33,7 +36,7 @@ defmodule Helix.Log.Event.Handler.Log do |> LogQuery.fetch() |> LogAction.revise(event.entity_id, event.message, event.version) - Event.emit(events) + Event.emit(events, from: event) end def log_forge_conclusion(event = %LogForgeCreateComplete{}) do @@ -43,6 +46,6 @@ defmodule Helix.Log.Event.Handler.Log do event.message, event.version) - Event.emit(events) + Event.emit(events, from: event) end end diff --git a/lib/process/action/top.ex b/lib/process/action/top.ex index 57cff48c..4c1f66fb 100644 --- a/lib/process/action/top.ex +++ b/lib/process/action/top.ex @@ -18,6 +18,7 @@ defmodule Helix.Process.Action.TOP do {:ok, [Process.t], [TOPRecalcadoEvent.t]} | {:error, :resources} + @typep relay :: Event.relay | nil @typep recalque_opts :: term @spec complete(Process.t) :: @@ -64,7 +65,7 @@ defmodule Helix.Process.Action.TOP do within the given server. A recalque must be performed every time the total available resources on the process changes. """ - def recalque(process_or_server, alloc_opts \\ []) + def recalque(process_or_server, opts \\ []) def recalque(%Process{gateway_id: gateway_id, target_id: gateway_id}, opts) do %{ @@ -83,13 +84,14 @@ defmodule Helix.Process.Action.TOP do @spec do_recalque(Server.id, recalque_opts) :: recalque_result - defp do_recalque(server_id, alloc_opts) do + defp do_recalque(server_id, opts) do resources = TOPQuery.load_top_resources(server_id) processes = ProcessQuery.get_processes_on_server(server_id) - case TOP.Allocator.allocate(server_id, resources, processes, alloc_opts) do + case TOP.Allocator.allocate(server_id, resources, processes, opts) do {:ok, allocation_result} -> - processes = schedule(allocation_result) + source = Keyword.get(opts, :source) + processes = schedule(allocation_result, source) event = TOPRecalcadoEvent.new(server_id) {:ok, processes, [event]} @@ -99,13 +101,13 @@ defmodule Helix.Process.Action.TOP do end end - @spec schedule(TOP.Allocator.allocation_successful) :: + @spec schedule(TOP.Allocator.allocation_successful, relay) :: [Process.t] docp """ Top-level guide that "interprets" the Allocation results and performs the required actions. """ - defp schedule(%{allocated: processes, dropped: _dropped}) do + defp schedule(%{allocated: processes, dropped: _dropped}, relay) do # Organize all processes in two groups: the local ones and the remote ones # A local process was started on this very server, while a remote process # was started somewhere else and *targets* this server. @@ -134,7 +136,7 @@ defmodule Helix.Process.Action.TOP do # track the completion date of the `next`-to-be-completed process. # Here we also deal with processes that were deemed already completed by the # simulation. - hespawn fn -> handle_forecast(forecast) end + hespawn fn -> handle_forecast(forecast, relay) end # Recreate the complete process list, filtering out the ones that were # already completed (see Forecast step above) @@ -174,19 +176,19 @@ defmodule Helix.Process.Action.TOP do processes end - @spec handle_forecast(TOP.Scheduler.forecast) :: + @spec handle_forecast(TOP.Scheduler.forecast, relay) :: term docp """ `handle_forecast` aggregates the `Scheduler.forecast/1` result and guides it to the corresponding handlers. Check `handle_completed/1` and `handle_next/1` for detailed explanation of each one. """ - defp handle_forecast(%{completed: completed, next: next}) do - handle_completed(completed) - handle_next(next) + defp handle_forecast(%{completed: completed, next: next}, relay) do + handle_completed(completed, relay) + handle_next(next, relay) end - @spec handle_completed([Process.t]) :: + @spec handle_completed([Process.t], relay) :: term docp """ `handle_completed` receives processes that according to `Schedule.forecast/1` @@ -207,17 +209,17 @@ defmodule Helix.Process.Action.TOP do Emits event. """ - defp handle_completed([]), + defp handle_completed([], _), do: :noop - defp handle_completed(completed) do + defp handle_completed(completed, source) do Enum.each(completed, fn completed_process -> with {:ok, events} <- complete(completed_process) do - Event.emit(events) + Event.emit(events, from: source) end end) end - @spec handle_next({Process.t, Process.time_left}) :: + @spec handle_next({Process.t, Process.time_left}, relay) :: term docp """ `handle_next` will receive the "next-to-be-completed" process, as defined by @@ -229,14 +231,14 @@ defmodule Helix.Process.Action.TOP do Emits TOPBringMeToLifeEvent.t after `time_left` seconds have elapsed. """ - defp handle_next({process, time_left}) do + defp handle_next({process, time_left}, _) do wake_me_up = TOPBringMeToLifeEvent.new(process) save_me = time_left * 1000 |> trunc() # Wakes me up inside Event.emit_after(wake_me_up, save_me) end - defp handle_next(_), + defp handle_next(_, _), do: :noop @spec handle_checkpoint([Process.t]) :: diff --git a/lib/process/event/handler/top.ex b/lib/process/event/handler/top.ex index 74a14b9d..021d40ea 100644 --- a/lib/process/event/handler/top.ex +++ b/lib/process/event/handler/top.ex @@ -61,7 +61,7 @@ defmodule Helix.Process.Event.Handler.TOP do {gateway_recalque :: boolean, target_recalque :: boolean} defp call_recalque(process = %Process{}, source_event) do %{gateway: gateway_recalque, target: target_recalque} = - TOPAction.recalque(process) + TOPAction.recalque(process, source: source_event) gateway_recalque = case gateway_recalque do diff --git a/lib/process/event/process.ex b/lib/process/event/process.ex index 2587eff5..aeab6d5f 100644 --- a/lib/process/event/process.ex +++ b/lib/process/event/process.ex @@ -69,6 +69,8 @@ defmodule Helix.Process.Event.Process do notify do + alias Helix.Process.Public.View.Process, as: ProcessView + @event :process_created @doc """ @@ -89,67 +91,26 @@ defmodule Helix.Process.Event.Process do Note that if third party `A` is connected to `S`, she can see the full process because of 1. Hence, this rule (3) only applies to third-parties connecting to the attack target. + + All this logic is handled by `ProcessView` and, under the hood, + `ProcessViewable`. """ def generate_payload(event = %_{confirmed: true}, socket) do - gateway_id = socket.assigns.gateway.server_id - destination_id = socket.assigns.destination.server_id - - cond do - # attacker AT attack_source; - # victim AT attack_target; - # player AT action_server; - gateway_id == destination_id -> - do_payload(event, socket) - - # attacker AT attack_target - event.gateway_id == gateway_id -> - do_payload(event, socket) - - # victim AT attack_source - event.target_id == gateway_id -> - do_payload(event, socket) - - # third AT attack_source - event.gateway_id == destination_id -> - do_payload(event, socket) - - # third AT attack_target - true -> - do_payload(event, socket, [partial: true]) - end - end - - # Internal event used for optimistic (asynchronous) processing - def generate_payload(%_{confirmed: false}, _), - do: :noreply - - defp do_payload(event, _socket, opts \\ []) do - file_id = event.process.file_id && to_string(event.process.file_id) - connection_id = - event.process.connection_id && to_string(event.process.connection_id) - - data = %{ - process_id: to_string(event.process.process_id), - type: to_string(event.process.type), - network_id: to_string(event.process.network_id), - file_id: file_id, - connection_id: connection_id, - source_ip: event.gateway_ip, - target_ip: event.target_ip - } + server_id = socket.assigns.destination.server_id + entity_id = socket.assigns.gateway.entity_id data = - if opts[:partial] do - data - |> Map.drop([:connection_id]) - |> Map.drop([:source_ip]) - else - data - end + ProcessView.render( + event.process.data, event.process, server_id, entity_id + ) {:ok, data} end + # Internal event used for optimistic (asynchronous) processing + def generate_payload(%_{confirmed: false}, _), + do: :noreply + @doc """ Both gateway and destination are notified. If they are the same, obviously notifies only one. diff --git a/lib/process/executable.ex b/lib/process/executable.ex index 5358ceba..729527d0 100644 --- a/lib/process/executable.ex +++ b/lib/process/executable.ex @@ -120,18 +120,18 @@ defmodule Helix.Process.Executable do defp call_process(function, params), do: apply(unquote(process), function, [params]) - @spec close_connection_on_fail(nil) :: :noop - @spec close_connection_on_fail(Connection.t) :: term + @spec close_connection_on_fail(nil, Event.relay) :: :noop + @spec close_connection_on_fail(Connection.t, Event.relay) :: term docp """ Helper called when `flow` of `execute/4` fails, and a connection may have to be closed as a result. """ - defp close_connection_on_fail(nil), + defp close_connection_on_fail(nil, _), do: :noop - defp close_connection_on_fail(connection) do + defp close_connection_on_fail(connection, relay) do connection |> TunnelAction.close_connection() - |> Event.emit() + |> Event.emit(from: relay) end @spec setup_connection(Server.t, Server.t, term, meta, {:create, term}) :: @@ -227,13 +227,13 @@ defmodule Helix.Process.Executable do quote location: :keep do - @spec execute(Server.t, Server.t, params, meta) :: + @spec execute(Server.t, Server.t, params, meta, Event.relay) :: {:ok, Process.t} | executable_error @doc """ Executes the process. """ - def execute(unquote_splicing(args)) do + def execute(unquote_splicing(args), relay) do process_data = get_process_data(unquote(params)) resources = get_resources(unquote_splicing(args)) file = get_file(unquote_splicing(args)) @@ -257,14 +257,14 @@ defmodule Helix.Process.Executable do {:ok, connection, events} <- setup_connection(unquote_splicing(args), connection_info), - on_success(fn -> Event.emit(events) end), - on_fail(fn -> close_connection_on_fail(connection) end), + on_success(fn -> Event.emit(events, from: relay) end), + on_fail(fn -> close_connection_on_fail(connection, relay) end), params = create_process_params(partial, connection), {:ok, process, events} <- ProcessAction.create(params), - on_success(fn -> Event.emit(events) end) + on_success(fn -> Event.emit(events, from: relay) end) do {:ok, process} else diff --git a/lib/process/process.ex b/lib/process/process.ex index e2710dc1..1ab1631a 100644 --- a/lib/process/process.ex +++ b/lib/process/process.ex @@ -27,7 +27,7 @@ defmodule Helix.Process do @doc """ Entry point for execution of the process. """ - defdelegate execute(gateway, target, params, meta), + defdelegate execute(gateway, target, params, meta, relay), to: __MODULE__.Executable @doc """ diff --git a/lib/software/action/flow/file.ex b/lib/software/action/flow/file.ex index 3ae1cc0f..f9d2d1a4 100644 --- a/lib/software/action/flow/file.ex +++ b/lib/software/action/flow/file.ex @@ -1,5 +1,6 @@ defmodule Helix.Software.Action.Flow.File do + alias Helix.Event alias Helix.Process.Model.Process alias Helix.Server.Model.Server alias Helix.Software.Model.File @@ -21,9 +22,11 @@ defmodule Helix.Software.Action.Flow.File do # implementation detail). @type bruteforce_execution_error :: BruteforceProcess.executable_error - @typep file_module :: File.Module.name + @type executable :: {File.t, File.Module.name} - @spec execute_file(File.t, file_module, Server.t, Server.t, params, meta) :: + @typep relay :: Event.relay + + @spec execute_file(executable, Server.t, Server.t, params, meta, relay) :: {:ok, Process.t} | executable_errors | {:error, :not_executable} @@ -37,10 +40,17 @@ defmodule Helix.Software.Action.Flow.File do If the process can not be started on the server, returns the corresponding error. """ - def execute_file(file = %File{}, module, gateway, target, params, meta) do - case {file, module} do + def execute_file( + executable = {%File{}, _}, + gateway = %Server{}, + target = %Server{}, + params, + meta, + relay) + do + case executable do {%File{software_type: :cracker}, :bruteforce} -> - BruteforceProcess.execute(gateway, target, params, meta) + BruteforceProcess.execute(gateway, target, params, meta, relay) # %File{software_type: :firewall} -> # FirewallFlow.execute(file, server, params) diff --git a/lib/software/action/flow/file/transfer.ex b/lib/software/action/flow/file/transfer.ex index c5b245c2..2f1f81a2 100644 --- a/lib/software/action/flow/file/transfer.ex +++ b/lib/software/action/flow/file/transfer.ex @@ -2,6 +2,7 @@ defmodule Helix.Software.Action.Flow.File.Transfer do import HELL.Macros + alias Helix.Event alias Helix.Network.Model.Net alias Helix.Process.Model.Process alias Helix.Process.Query.Process, as: ProcessQuery @@ -10,24 +11,54 @@ defmodule Helix.Software.Action.Flow.File.Transfer do alias Helix.Software.Model.Storage alias Helix.Software.Process.File.Transfer, as: FileTransferProcess + @type transfer_result :: + {:ok, Process.t} + | transfer_error + + @type transfer_error :: + FileTransferProcess.executable_error + @typep type :: :download | :pftp_download | :upload - @type transfer_error :: FileTransferProcess.executable_error + @typep relay :: Event.relay - @spec transfer(type, Server.t, Server.t, File.t, Storage.t, Net.t) :: - {:ok, Process.t} - | transfer_error + @spec download(Server.t, Server.t, File.t, Storage.t, Net.t, relay) :: + transfer_result + @doc """ + Starts a FileDownload process. + """ + def download(gateway, endpoint, file, storage, net, relay), + do: transfer(:download, gateway, endpoint, file, storage, net, relay) + + @spec upload(Server.t, Server.t, File.t, Storage.t, Net.t, relay) :: + transfer_result @doc """ + Starts a FileUpload process. + """ + def upload(gateway, endpoint, file, storage, net, relay), + do: transfer(:upload, gateway, endpoint, file, storage, net, relay) + + @spec pftp_download(Server.t, Server.t, File.t, Storage.t, Net.t, relay) :: + transfer_result + @doc """ + Starts a PFTPDownload process. + """ + def pftp_download(gateway, endpoint, file, storage, net, relay), + do: transfer(:pftp_download, gateway, endpoint, file, storage, net, relay) + + @spec transfer(type, Server.t, Server.t, File.t, Storage.t, Net.t, relay) :: + transfer_result + docp """ Starts a FileTransfer process, which can be one of [pftp_]download or upload. If that exact file is already being transferred to/by the gateway, the existing process is returned and no new transfer is created. This ensures the same file cannot be transferred multiple times to/from the same server. """ - def transfer(type, gateway, endpoint, file, storage, net) do + defp transfer(type, gateway, endpoint, file, storage, net, relay) do {_, process_type, _} = get_type_info(type) # Verifies whether that file is already being transferred to/by the gateway @@ -45,17 +76,16 @@ defmodule Helix.Software.Action.Flow.File.Transfer do # There's no transfer yet. We'll have to create a new one. nil -> - new_transfer(type, gateway, endpoint, file, storage, net) + do_transfer(type, gateway, endpoint, file, storage, net, relay) end end - @spec new_transfer(type, Server.t, Server.t, File.t, Storage.t, Net.t) :: - {:ok, Process.t} - | transfer_error + @spec do_transfer(type, Server.t, Server.t, File.t, Storage.t, Net.t, relay) :: + transfer_result docp """ Starts a FileTransfer process, which can be one of download or upload. """ - defp new_transfer(type, gateway, endpoint, file, storage, net) do + defp do_transfer(type, gateway, endpoint, file, storage, net, relay) do {connection_type, process_type, transfer_type} = get_type_info(type) params = %{ @@ -71,7 +101,7 @@ defmodule Helix.Software.Action.Flow.File.Transfer do type: process_type } - FileTransferProcess.execute(gateway, endpoint, params, meta) + FileTransferProcess.execute(gateway, endpoint, params, meta, relay) end docp """ diff --git a/lib/software/event/file.ex b/lib/software/event/file.ex index 17dabcd3..887f085a 100644 --- a/lib/software/event/file.ex +++ b/lib/software/event/file.ex @@ -60,11 +60,13 @@ defmodule Helix.Software.Event.File do Notifies the Client that a file has been downloaded. """ + alias Helix.Software.Public.Index, as: SoftwareIndex + @event :file_downloaded def generate_payload(event, _socket) do data = %{ - file: event.file.id + file: SoftwareIndex.render_file(event.file) } {:ok, data} diff --git a/lib/software/event/handler/file/transfer.ex b/lib/software/event/handler/file/transfer.ex index e3088192..ba912188 100644 --- a/lib/software/event/handler/file/transfer.ex +++ b/lib/software/event/handler/file/transfer.ex @@ -48,7 +48,7 @@ defmodule Helix.Software.Event.Handler.File.Transfer do event |> get_event(:failed, error) - |> Event.emit() + |> Event.emit(from: event) {:error, error} end diff --git a/lib/software/public/file.ex b/lib/software/public/file.ex index 7cf5a5b7..2a222db8 100644 --- a/lib/software/public/file.ex +++ b/lib/software/public/file.ex @@ -1,5 +1,6 @@ defmodule Helix.Software.Public.File do + alias Helix.Event alias Helix.Network.Model.Net alias Helix.Network.Model.Network alias Helix.Network.Model.Tunnel @@ -15,7 +16,9 @@ defmodule Helix.Software.Public.File do | {:error, {:storage, :not_found}} | {:error, :internal} - @spec download(Server.t, Server.t, Tunnel.t, Storage.t, File.t) :: + @typep relay :: Event.relay + + @spec download(Server.t, Server.t, Tunnel.t, Storage.t, File.t, relay) :: {:ok, Process.t} | FileTransferFlow.transfer_error @doc """ @@ -27,12 +30,13 @@ defmodule Helix.Software.Public.File do target = %Server{}, tunnel = %Tunnel{}, storage = %Storage{}, - file = %File{}) + file = %File{}, + relay) do net = Net.new(tunnel) transfer = - FileTransferFlow.transfer(:download, gateway, target, file, storage, net) + FileTransferFlow.download(gateway, target, file, storage, net, relay) case transfer do {:ok, process} -> @@ -47,9 +51,9 @@ defmodule Helix.Software.Public.File do File.t_of_type(:cracker), gateway :: Server.t, target :: Server.t, - Network.id, - Network.ip, - term) + target_nip :: {Network.id, Network.ip}, + term, + relay) :: {:ok, Process.t} | FileFlow.bruteforce_execution_error @@ -61,9 +65,9 @@ defmodule Helix.Software.Public.File do cracker = %File{software_type: :cracker}, gateway = %Server{}, target = %Server{}, - network_id = %Network.ID{}, - target_ip, - bounce_id) + {network_id = %Network.ID{}, target_ip}, + bounce_id, + relay) do params = %{ target_server_ip: target_ip @@ -75,6 +79,8 @@ defmodule Helix.Software.Public.File do cracker: cracker } - FileFlow.execute_file(cracker, :bruteforce, gateway, target, params, meta) + FileFlow.execute_file( + {cracker, :bruteforce}, gateway, target, params, meta, relay + ) end end diff --git a/lib/software/public/index.ex b/lib/software/public/index.ex index 4138a7b2..194fe4b5 100644 --- a/lib/software/public/index.ex +++ b/lib/software/public/index.ex @@ -3,7 +3,6 @@ defmodule Helix.Software.Public.Index do alias Helix.Cache.Query.Cache, as: CacheQuery alias Helix.Server.Model.Server alias Helix.Software.Model.File - alias Helix.Software.Public.View.File, as: FileView alias Helix.Software.Query.Storage, as: StorageQuery @type index :: @@ -42,28 +41,25 @@ defmodule Helix.Software.Public.Index do # %{"foo" => [1, 2]} Map.merge(acc, el, fn _k, v1, v2 -> v1 ++ v2 end) end) - |> Enum.map(fn {path, files} -> - {path, Enum.map(files, &FileView.render/1)} - end) - |> :maps.from_list() end @spec index(index) :: rendered_index def render_index(index) do Enum.reduce(index, %{}, fn {folder, files}, acc -> - rendered_files = - Enum.map(files, fn entry -> - %{ - file_id: to_string(entry.file_id), - path: entry.path, - size: entry.size, - software_type: to_string(entry.software_type), - modules: entry.modules - } - end) + rendered_files = Enum.map(files, &render_file/1) Map.put(acc, folder, rendered_files) end) end + + def render_file(file = %File{}) do + %{ + file_id: to_string(file.file_id), + path: file.full_path, + size: file.file_size, + software_type: to_string(file.software_type), + modules: file.modules + } + end end diff --git a/lib/software/public/pftp.ex b/lib/software/public/pftp.ex index 543bd868..dda76b60 100644 --- a/lib/software/public/pftp.ex +++ b/lib/software/public/pftp.ex @@ -3,6 +3,7 @@ defmodule Helix.Software.Public.PFTP do Public layer of the PublicFTP feature -- shortened to PFTP to avoid confusion. """ + alias Helix.Event alias Helix.Network.Model.Net alias Helix.Network.Query.Network, as: NetworkQuery alias Helix.Process.Model.Process @@ -97,7 +98,7 @@ defmodule Helix.Software.Public.PFTP do end) end - @spec download(Server.t, Server.t, Storage.t, File.t) :: + @spec download(Server.t, Server.t, Storage.t, File.t, Event.relay) :: {:ok, Process.t} | FileTransferFlow.transfer_error @doc """ @@ -107,7 +108,8 @@ defmodule Helix.Software.Public.PFTP do gateway = %Server{}, destination = %Server{}, storage = %Storage{}, - file = %File{}) + file = %File{}, + relay) do # PFTP downloads are "public", so must always happen over the internet. network_id = @internet_id @@ -115,8 +117,8 @@ defmodule Helix.Software.Public.PFTP do net = Net.new(network_id, []) transfer = - FileTransferFlow.transfer( - :pftp_download, gateway, destination, file, storage, net + FileTransferFlow.pftp_download( + gateway, destination, file, storage, net, relay ) case transfer do diff --git a/lib/software/public/view/file.ex b/lib/software/public/view/file.ex deleted file mode 100644 index 37d9ef7b..00000000 --- a/lib/software/public/view/file.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule Helix.Software.Public.View.File do - - alias Helix.Software.Model.File - - @spec render(File.t) :: - %{ - file_id: File.id, - path: File.path, - size: File.size, - software_type: File.type, - modules: File.modules - } - def render(file = %File{}) do - %{ - file_id: file.file_id, - path: file.full_path, - size: file.file_size, - software_type: file.software_type, - modules: %{} - } - end -end diff --git a/lib/software/websocket/requests/cracker/bruteforce.ex b/lib/software/websocket/requests/cracker/bruteforce.ex index caa9bc89..d7e53832 100644 --- a/lib/software/websocket/requests/cracker/bruteforce.ex +++ b/lib/software/websocket/requests/cracker/bruteforce.ex @@ -65,9 +65,12 @@ request Helix.Software.Websocket.Requests.Cracker.Bruteforce do cracker = request.meta.cracker gateway = request.meta.gateway target = request.meta.target + relay = request.relay bruteforce = - FilePublic.bruteforce(cracker, gateway, target, network_id, ip, bounces) + FilePublic.bruteforce( + cracker, gateway, target, {network_id, ip}, bounces, relay + ) case bruteforce do {:ok, process} -> @@ -83,7 +86,7 @@ request Helix.Software.Websocket.Requests.Cracker.Bruteforce do end end - render_process() + render_empty() defp cast_bounces(bounces) when is_list(bounces), do: {:ok, Enum.map(bounces, &(Server.ID.cast!(&1)))} diff --git a/lib/software/websocket/requests/file/download.ex b/lib/software/websocket/requests/file/download.ex index dc87538e..6c49f630 100644 --- a/lib/software/websocket/requests/file/download.ex +++ b/lib/software/websocket/requests/file/download.ex @@ -86,17 +86,21 @@ request Helix.Software.Websocket.Requests.File.Download do tunnel = socket.assigns.tunnel gateway = request.meta.gateway destination = request.meta.destination + relay = request.relay - case FilePublic.download(gateway, destination, tunnel, storage, file) do - {:ok, process} -> - update_meta(request, %{process: process}, reply: true) + download = + FilePublic.download(gateway, destination, tunnel, storage, file, relay) + + case download do + {:ok, _process} -> + reply_ok(request) {:error, reason} -> reply_error(reason) end end - render_process() + render_empty() @spec get_error(reason :: {term, term} | term) :: String.t diff --git a/lib/software/websocket/requests/pftp/file/download.ex b/lib/software/websocket/requests/pftp/file/download.ex index a2039765..c4e8b503 100644 --- a/lib/software/websocket/requests/pftp/file/download.ex +++ b/lib/software/websocket/requests/pftp/file/download.ex @@ -85,9 +85,9 @@ request Helix.Software.Websocket.Requests.PFTP.File.Download do destination = request.meta.destination file = request.meta.file storage = request.meta.storage + relay = request.relay - # - case PFTPPublic.download(gateway, destination, storage, file) do + case PFTPPublic.download(gateway, destination, storage, file, relay) do {:ok, process} -> update_meta(request, %{process: process}, reply: true) @@ -96,5 +96,5 @@ request Helix.Software.Websocket.Requests.PFTP.File.Download do end end - render_process() + render_empty() end diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index fed1ac68..aad0bd04 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -15,6 +15,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Universe.Bank.Model.BankToken alias Helix.Universe.Bank.Query.Bank, as: BankQuery + @typep relay :: Event.relay + @doc """ Starts the `bank_reveal_password` process. @@ -26,10 +28,10 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Emits: ProcessCreatedEvent """ - @spec reveal_password(BankAccount.t, BankToken.id, Server.t, Server.t) :: + @spec reveal_password(BankAccount.t, BankToken.id, Server.t, Server.t, relay) :: {:ok, Process.t} | BankAccountRevealPasswordProcess.executable_error - def reveal_password(account, token_id, gateway, atm) do + def reveal_password(account, token_id, gateway, atm, relay) do params = %{ token_id: token_id, account: account @@ -40,7 +42,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do bounce: [] } - BankAccountRevealPasswordProcess.execute(gateway, atm, params, meta) + BankAccountRevealPasswordProcess.execute(gateway, atm, params, meta, relay) end @doc """ diff --git a/lib/universe/bank/action/flow/bank_transfer.ex b/lib/universe/bank/action/flow/bank_transfer.ex index 96799e7a..a2c69fbd 100644 --- a/lib/universe/bank/action/flow/bank_transfer.ex +++ b/lib/universe/bank/action/flow/bank_transfer.ex @@ -1,5 +1,7 @@ +# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity defmodule Helix.Universe.Bank.Action.Flow.BankTransfer do + alias Helix.Event alias Helix.Account.Model.Account alias Helix.Network.Model.Net alias Helix.Process.Model.Process @@ -14,9 +16,10 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransfer do from_account :: BankAccount.t, to_account :: BankAccount.t, amount :: BankTransfer.amount, - started_by :: Account.idt, + started_by :: Account.t, gateway :: Server.t, - Net.t) + Net.t, + Event.relay) :: {:ok, Process.t} | {:error, {:funds, :insufficient}} @@ -29,7 +32,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransfer do `BankAction.start_transfer()`, it also is responsible for creating the transfer process to be managed by TOP. """ - def start(from_account, to_account, amount, started_by, gateway, net) do + def start(from_account, to_account, amount, started_by, gateway, net, relay) do start_transfer = fn -> BankAction.start_transfer( from_account, to_account, amount, started_by.account_id @@ -60,7 +63,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransfer do bounce: bounces } - BankTransferProcess.execute(gateway, target_atm, params, meta) + BankTransferProcess.execute(gateway, target_atm, params, meta, relay) else error = {:error, {_, _}} -> diff --git a/lib/websocket/request.ex b/lib/websocket/request.ex index a8b044d2..084f05ec 100644 --- a/lib/websocket/request.ex +++ b/lib/websocket/request.ex @@ -15,20 +15,27 @@ defmodule Helix.Websocket.Request do import HELL.Macros alias Helix.Websocket.Utils, as: WebsocketUtils + alias Helix.Websocket.Request.Relay, as: RequestRelay + + @type t :: t(struct) @type t(struct) :: %{ __struct__: struct, unsafe: map, - params: map, - meta: map + params: params, + meta: meta, + relay: RequestRelay.t } + @type params :: map + @type meta :: map + @doc """ Top-level macro for creating a Websocket Request, which can be handled by any channel. It must implement the Requestable protocol. """ defmacro request(name, do: block) do - quote do + quote location: :keep do defmodule unquote(name) do @moduledoc false @@ -37,14 +44,15 @@ defmodule Helix.Websocket.Request do @type t :: Helix.Websocket.Request.t(__MODULE__) - @enforce_keys [:unsafe] - defstruct [:unsafe, params: %{}, meta: %{}] + @enforce_keys [:unsafe, :relay] + defstruct [:unsafe, :relay, params: %{}, meta: %{}] @spec new(term) :: t def new(params \\ %{}) do %__MODULE__{ - unsafe: params + unsafe: params, + relay: RequestRelay.new(params) } end @@ -107,4 +115,33 @@ defmodule Helix.Websocket.Request do end end + +end + +defmodule Helix.Websocket.Request.Relay do + @moduledoc """ + `RequestRelay` is a struct intended to be relayed all the way from the Request + to the Public to the ActionFlow, so it can be used by `Helix.Event` to + identify the `request_id` and create a meaningful stacktrace. + """ + + defstruct [:request_id] + + @type t :: t_of_type(binary) + + @type t_of_type(type) :: + %__MODULE__{ + request_id: type + } + + @spec new(map) :: + t + | t_of_type(nil) + def new(%{"request_id" => request_id}) when is_binary(request_id) do + %__MODULE__{ + request_id: request_id + } + end + def new(_), + do: %__MODULE__{} end diff --git a/lib/websocket/requestable.ex b/lib/websocket/requestable.ex index b3df74db..06c562e9 100644 --- a/lib/websocket/requestable.ex +++ b/lib/websocket/requestable.ex @@ -38,10 +38,10 @@ defprotocol Helix.Websocket.Requestable do an actual Channel socket. """ - alias Phoenix.Socket + alias Helix.Websocket alias Helix.Websocket.Request - @spec check_params(Request.t(term), Socket.t) :: + @spec check_params(Request.t(term), Websocket.t) :: {:ok, Request.t(term)} | {:error, term} @doc """ @@ -58,7 +58,7 @@ defprotocol Helix.Websocket.Requestable do """ def check_params(request, socket) - @spec check_permissions(Request.t(term), Socket.t) :: + @spec check_permissions(Request.t(term), Websocket.t) :: {:ok, Request.t(term)} | {:error, term} @doc """ @@ -76,7 +76,7 @@ defprotocol Helix.Websocket.Requestable do """ def check_permissions(request, socket) - @spec handle_request(Request.t(term), Socket.t) :: + @spec handle_request(Request.t(term), Websocket.t) :: {:ok, Request.t(term)} | {:error, term} @doc """ @@ -94,9 +94,10 @@ defprotocol Helix.Websocket.Requestable do """ def handle_request(request, socket) - @spec reply(Request.t(term), Socket.t) :: + @spec reply(Request.t(term), Websocket.t) :: {:ok, reply :: map} | {:error, reply :: map} + | {:stop, reason :: term} | :noreply @doc """ Final step of the flow, which is only reached when all previous steps were diff --git a/lib/websocket/requests_channel.ex b/lib/websocket/requests_channel.ex deleted file mode 100644 index c140ff3a..00000000 --- a/lib/websocket/requests_channel.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Helix.Websocket.RequestsChannel do - - use Phoenix.Channel - - alias Helix.Account.Websocket.Routes, as: AccountRoutes - - def join(_topic, _message, socket) do - # God in the command - {:ok, socket} - end - - def handle_in("account.logout", _params, socket), - do: AccountRoutes.account_logout(socket) - - def handle_in(_, _, socket) do - {:reply, :error, socket} - end -end diff --git a/lib/websocket/utils.ex b/lib/websocket/utils.ex index 571eb2bb..6ba3ed91 100644 --- a/lib/websocket/utils.ex +++ b/lib/websocket/utils.ex @@ -1,26 +1,11 @@ defmodule Helix.Websocket.Utils do alias HELL.Utils + alias Helix.Websocket alias Helix.Process.Model.Process alias Helix.Process.Public.View.Process, as: ProcessView - @type socket :: term - - @type reply_ok :: - {:reply, {:ok, term}, socket} - - @type reply_error :: - {:reply, {:error, %{data: term}}, socket} - - @type no_reply :: - {:noreply, socket} - - @spec no_reply(socket) :: - no_reply - def no_reply(socket), - do: {:noreply, socket} - - @spec render_process(Process.t, socket) :: + @spec render_process(Process.t, Websocket.t) :: %{data: map} @doc """ Helper that automatically renders the reply with the recently created process. @@ -30,22 +15,28 @@ defmodule Helix.Websocket.Utils do server_id = socket.assigns.gateway.server_id entity_id = socket.assigns.entity_id - pview = ProcessView.render(process_data, process, server_id, entity_id) - - %{data: pview} + ProcessView.render(process_data, process, server_id, entity_id) end - @spec reply_ok(term, socket) :: - reply_ok - def reply_ok(data, socket), - do: {:reply, {:ok, wrap_data(data)}, socket} + @spec reply_ok(Websocket.payload, Websocket.t) :: + Websocket.reply_ok + def reply_ok(payload, socket), + do: {:reply, {:ok, payload}, socket} + + @spec reply_error(Websocket.payload, Websocket.t) :: + Websocket.reply_error + def reply_error(payload, socket), + do: {:reply, {:error, payload}, socket} - @spec reply_error(term, socket) :: - reply_error - def reply_error(msg, socket) when is_binary(msg), - do: reply_error(%{data: %{message: msg}}, socket) - def reply_error(error, socket), - do: {:reply, {:error, wrap_data(error)}, socket} + @spec stop(term, Websocket.t) :: + Websocket.reply_stop + def stop(reason, socket), + do: {:stop, reason, socket} + + @spec no_reply(Websocket.t) :: + Websocket.no_reply + def no_reply(socket), + do: {:noreply, socket} @spec wrap_data(data) :: data @@ -56,10 +47,10 @@ defmodule Helix.Websocket.Utils do def wrap_data(data), do: %{data: data} - @spec internal_error(socket) :: - reply_error - def internal_error(socket), - do: reply_error("internal", socket) + @spec reply_internal_error(Websocket.t) :: + Websocket.reply_error + def reply_internal_error(socket), + do: reply_error(%{data: %{message: "internal"}}, socket) @doc """ General purpose error code translator. If you want to specify or handle a diff --git a/lib/websocket/websocket.ex b/lib/websocket/websocket.ex index 22030f35..aabc53e4 100644 --- a/lib/websocket/websocket.ex +++ b/lib/websocket/websocket.ex @@ -5,16 +5,31 @@ defmodule Helix.Websocket do alias Phoenix.Socket alias Helix.Event.Notificable alias Helix.Websocket.Joinable + alias Helix.Websocket.Request alias Helix.Websocket.Requestable alias Helix.Websocket.Utils, as: WebsocketUtils alias Helix.Account.Action.Session, as: SessionAction alias Helix.Entity.Query.Entity, as: EntityQuery - @typep socket :: Socket.t + @type replies :: + reply_ok + | reply_error + | reply_stop + | no_reply + + @type reply_ok :: {:reply, {:ok, payload}, socket} + @type reply_error :: {:reply, {:error, payload}, socket} + @type reply_stop :: {:stop, term, socket} + @type no_reply :: {:noreply, socket} + + @type meta :: %{request_id: binary | nil} + @type payload :: %{data: term, meta: meta} | %{data: term} + + @type socket :: Socket.t + @type t :: socket transport :websocket, Phoenix.Transports.WebSocket - channel "requests", Helix.Websocket.RequestsChannel channel "account:*", Helix.Account.Websocket.Channel.Account channel "server:*", Helix.Server.Websocket.Channel.Server @@ -35,9 +50,8 @@ defmodule Helix.Websocket do end end - def connect(_, _) do - :error - end + def connect(_, _), + do: :error def id(socket), do: "session:" <> socket.assigns.session @@ -64,7 +78,6 @@ defmodule Helix.Websocket do Generic request handler. It guides the request through the Requestable flow, replying the result back to the client. """ - # TODO: Adicionar ReqMeta aqui \/; passar diretamente p/ `handle_request/2` def handle_request(request, socket) do with \ {:ok, request} <- Requestable.check_params(request, socket), @@ -73,25 +86,52 @@ defmodule Helix.Websocket do do request |> Requestable.reply(socket) - |> reply_request(socket) + |> handle_response(request, socket) else - {:error, %{message: msg}} -> - WebsocketUtils.reply_error(msg, socket) + error = {:error, %{message: _}} -> + error + |> handle_response(request, socket) + _ -> - WebsocketUtils.internal_error(socket) + WebsocketUtils.reply_internal_error(socket) end end - @spec reply_request({:ok | :error} | :noreply, socket) :: - {:reply, {:ok, %{data: term}}, socket} - | {:reply, {:error, %{data: term}}, socket} - | {:noreply, socket} - defp reply_request({:ok, data}, socket), - do: WebsocketUtils.reply_ok(data, socket) - defp reply_request({:error, data}, socket), - do: WebsocketUtils.reply_error(data, socket) - defp reply_request(:noreply, socket), + @spec generate_payload(term, Request.t) :: + payload + defp generate_payload(data, request) do + %{} + |> Map.merge(WebsocketUtils.wrap_data(data)) + |> Map.merge(%{meta: generate_meta(request)}) + end + + @spec generate_meta(Request.t) :: + meta + defp generate_meta(%{relay: %{request_id: request_id}}), + do: %{request_id: request_id} + + @spec reply_request({:ok | :error, payload}, socket) :: + {:reply, {:ok, payload}, socket} + | {:reply, {:error, payload}, socket} + defp reply_request({:ok, payload}, socket), + do: WebsocketUtils.reply_ok(payload, socket) + defp reply_request({:error, payload}, socket), + do: WebsocketUtils.reply_error(payload, socket) + + @spec handle_response({:stop, term}, Request.t, socket) :: reply_stop + @spec handle_response(:noreply, Request.t, socket) :: no_reply + @spec handle_response({:ok | :error, term}, Request.t, socket) :: + reply_ok + | reply_error + defp handle_response({:stop, reason}, _request, socket), + do: WebsocketUtils.stop(reason, socket) + defp handle_response(:noreply, _, socket), do: WebsocketUtils.no_reply(socket) + defp handle_response({status, data}, request, socket) do + payload = generate_payload(data, request) + + reply_request({status, payload}, socket) + end @doc """ Generic notification ("event going out") handler. It guides the notification diff --git a/test/account/websocket/channel/account/requests/logout_test.exs b/test/account/websocket/channel/account/requests/logout_test.exs new file mode 100644 index 00000000..54e9538b --- /dev/null +++ b/test/account/websocket/channel/account/requests/logout_test.exs @@ -0,0 +1,30 @@ +defmodule Helix.Account.Websocket.Channel.Account.Requests.LogoutTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Websocket + + alias Helix.Test.Channel.Setup, as: ChannelSetup + + @endpoint Helix.Endpoint + + describe "bootstrap" do + test "returns expected result" do + {socket, %{token: token}} = ChannelSetup.join_account() + + # Request logout + push socket, "account.logout", %{} + + # Wait process teardown. Required + :timer.sleep(50) + + # Channel no longer exists + refute Process.alive? socket.channel_pid + + # The token has been invalidated so we should not be able to use it again + assert :error == connect(Websocket, %{token: token}) + end + end +end diff --git a/test/account/websocket/routes_test.exs b/test/account/websocket/routes_test.exs deleted file mode 100644 index 69dd6179..00000000 --- a/test/account/websocket/routes_test.exs +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Helix.Account.Websocket.RoutesTest do - - use Helix.Test.Case.Integration - - alias Helix.Websocket - alias Helix.Account.Action.Session, as: SessionAction - - alias Helix.Test.Account.Factory - - import Phoenix.ChannelTest - - @endpoint Helix.Endpoint - - setup do - account = Factory.insert(:account) - {:ok, token} = SessionAction.generate_token(account) - {:ok, socket} = connect(Websocket, %{token: token}) - {:ok, _, socket} = join(socket, "requests") - - {:ok, account: account, token: token, socket: socket} - end - - test "logout closes socket", context do - push(context.socket, "account.logout") - - # Wait process teardown. - :timer.sleep(100) - - refute Process.alive? context.socket.channel_pid - # The token has been invalidated so we should not be able to use it again - assert :error == connect(Websocket, %{token: context.token}) - end -end diff --git a/test/event/loggable/flow_test.exs b/test/event/loggable/flow_test.exs index e00896a7..66f63627 100644 --- a/test/event/loggable/flow_test.exs +++ b/test/event/loggable/flow_test.exs @@ -23,7 +23,9 @@ defmodule Helix.Event.Loggable.FlowTest do msg = "foobar" entry = LoggableFlow.build_entry(server.server_id, entity.entity_id, msg) - assert :ok == LoggableFlow.save(entry) + # Saves the entry; returns LogCreatedEvent + assert [event] = LoggableFlow.save(entry) + assert event.__struct__ == Helix.Log.Event.Log.Created [log] = LogQuery.get_logs_on_server(server) assert log.message == msg @@ -32,7 +34,7 @@ defmodule Helix.Event.Loggable.FlowTest do end test "performs a noop on empty list" do - assert :ok == LoggableFlow.save([]) + assert [] == LoggableFlow.save([]) end end end diff --git a/test/event/notification_handler_test.exs b/test/event/notification_handler_test.exs index 11edab50..a917876e 100644 --- a/test/event/notification_handler_test.exs +++ b/test/event/notification_handler_test.exs @@ -7,7 +7,7 @@ defmodule Helix.Event.NotificationHandlerTest do import Helix.Test.Macros import Helix.Test.Event.Macros - alias Helix.Process.Query.Process, as: ProcessQuery + alias Helix.Process.Model.Process alias Helix.Test.Channel.Setup, as: ChannelSetup alias Helix.Test.Process.TOPHelper @@ -28,9 +28,14 @@ defmodule Helix.Event.NotificationHandlerTest do describe "notification_handler/1" do test "notifies gateway that a process was created (single-server)" do {_socket, %{gateway: gateway}} = - ChannelSetup.join_server([own_server: true]) + ChannelSetup.join_server(own_server: true) - event = EventSetup.Process.created(gateway.server_id) + event = + EventSetup.Process.created( + gateway_id: gateway.server_id, + target_id: gateway.server_id, + type: :bruteforce + ) # Process happens on the same server assert event.gateway_id == event.target_id @@ -46,14 +51,15 @@ defmodule Helix.Event.NotificationHandlerTest do assert_push "event", notification, timeout() assert notification.event == "process_created" + process = notification.data + # Make sure all we need is on the process return - assert_id notification.data.process_id, event.process.process_id - assert notification.data.type == event.process.type |> to_string() - assert_id notification.data.file_id, event.process.file_id - assert_id notification.data.connection_id, event.process.connection_id - assert_id notification.data.network_id, event.process.network_id - assert notification.data.target_ip - assert notification.data.source_ip + assert_id process.process_id, event.process.process_id + assert process.type == event.process.type |> to_string() + assert_id process.file.id, event.process.file_id + assert_id process.access.connection_id, event.process.connection_id + assert_id process.network_id, event.process.network_id + assert process.target_ip # Event id was generated assert notification.meta.event_id @@ -71,7 +77,11 @@ defmodule Helix.Event.NotificationHandlerTest do assert_broadcast "event", _, timeout() event = - EventSetup.Process.created(gateway.server_id, destination.server_id) + EventSetup.Process.created( + gateway_id: gateway.server_id, + target_id: destination.server_id, + type: :bruteforce + ) # Process happens on two different servers refute event.gateway_id == event.target_id @@ -87,14 +97,15 @@ defmodule Helix.Event.NotificationHandlerTest do assert_push "event", notification, timeout() assert notification.event == "process_created" + process = notification.data + # Make sure all we need is on the process return - assert_id notification.data.process_id, event.process.process_id - assert notification.data.type == event.process.type |> to_string() - assert_id notification.data.file_id, event.process.file_id - assert_id notification.data.connection_id, event.process.connection_id - assert_id notification.data.network_id, event.process.network_id - assert notification.data.target_ip - assert notification.data.source_ip + assert_id process.process_id, event.process.process_id + assert process.type == event.process.type |> to_string() + assert_id process.file.id, event.process.file_id + assert_id process.access.connection_id, event.process.connection_id + assert_id process.network_id, event.process.network_id + assert process.target_ip # Event id was generated assert notification.meta.event_id @@ -131,20 +142,17 @@ defmodule Helix.Event.NotificationHandlerTest do # Start the Bruteforce attack ref = push socket, "cracker.bruteforce", params - assert_reply ref, :ok, response, timeout(:slow) - - # The response includes the Bruteforce process information - assert response.data.process_id + assert_reply ref, :ok, %{}, timeout(:slow) # Wait for generic ProcessCreatedEvent assert_push "event", _top_recalcado_event, timeout() assert_push "event", process_created_event, timeout() + assert process_created_event.event == "process_created" + process_id = Process.ID.cast!(process_created_event.data.process_id) # Let's cheat and finish the process right now - process = ProcessQuery.fetch(response.data.process_id) - process_id = process.process_id - TOPHelper.force_completion(process) + TOPHelper.force_completion(process_id) # Intercept Helix internal events. # Note these events won't (necessarily) go out to the Client, they will diff --git a/test/features/file/transfer_test.exs b/test/features/file/transfer_test.exs new file mode 100644 index 00000000..d050e414 --- /dev/null +++ b/test/features/file/transfer_test.exs @@ -0,0 +1,94 @@ +defmodule Helix.Test.Features.File.TransferTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + import Helix.Test.Macros + + alias Helix.Process.Query.Process, as: ProcessQuery + alias Helix.Software.Model.File + alias Helix.Software.Query.File, as: FileQuery + + alias HELL.TestHelper.Random + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Software.Helper, as: SoftwareHelper + alias Helix.Test.Software.Setup, as: SoftwareSetup + + @moduletag :feature + + describe "file.download" do + test "download lifecycle" do + {socket, %{gateway: gateway, destination: destination}} = + ChannelSetup.join_server() + + # Connect to gateway channel too, so we can receive gateway notifications + ChannelSetup.join_server(socket: socket, own_server: true) + + gateway_storage = SoftwareHelper.get_storage(gateway) + {dl_file, _} = SoftwareSetup.file(server_id: destination.server_id) + + request_id = Random.string(max: 256) + + params = + %{ + "file_id" => dl_file.file_id |> to_string(), + "request_id" => request_id + } + + ref = push socket, "file.download", params + assert_reply ref, :ok, response, timeout(:slow) + + # Download is acknowledge (`:ok`). Contains the `request_id`. + assert response.meta.request_id == request_id + assert response.data == %{} + + # After a while, client receives the new event through top recalque + assert_push "event", l_top_recalcado_event, timeout(:fast) + assert_push "event", _r_top_recalcado_event, timeout(:fast) + assert_push "event", l_process_created_event, timeout(:fast) + assert_push "event", _r_process_created_event, timeout(:fast) + + # Each one have the client-defined request_id + assert l_top_recalcado_event.event == "top_recalcado" + assert l_top_recalcado_event.meta.request_id == request_id + + assert l_process_created_event.event == "process_created" + assert l_process_created_event.meta.request_id == request_id + + # Force completion of the process + # Due to forced completion, we won't have the `request_id` information + # on the upcoming events available on our tests. But they should exist on + # real life. + process = ProcessQuery.fetch(l_process_created_event.data.process_id) + TOPHelper.force_completion(process) + + # Note we are subscribed to events on both the `gateway` and `destination` + assert_push "event", _r_log_created_event, timeout(:fast) + assert_push "event", _l_log_created_event, timeout(:fast) + assert_push "event", file_downloaded_event, timeout(:fast) + assert_push "event", _l_process_completed, timeout(:fast) + assert_push "event", _r_process_completed, timeout(:fast) + + assert file_downloaded_event.event == "file_downloaded" + + # Process no longer exists + refute ProcessQuery.fetch(process.process_id) + + # The new file exists on my server + file = + file_downloaded_event.data.file.file_id + |> File.ID.cast!() + |> FileQuery.fetch() + + assert file.storage_id == gateway_storage.storage_id + + # The old file still exists on the target server, as expected + r_file = FileQuery.fetch(dl_file.file_id) + + assert r_file.storage_id == SoftwareHelper.get_storage_id(destination) + + TOPHelper.top_stop(gateway) + end + end +end diff --git a/test/features/hack_test.exs b/test/features/hack_test.exs index 89264cb7..0c1db2f5 100644 --- a/test/features/hack_test.exs +++ b/test/features/hack_test.exs @@ -8,7 +8,9 @@ defmodule Helix.Test.Features.Hack do alias HELL.Utils alias Helix.Entity.Query.Database, as: DatabaseQuery + alias Helix.Network.Model.Connection alias Helix.Network.Query.Tunnel, as: TunnelQuery + alias Helix.Process.Model.Process alias Helix.Process.Query.Process, as: ProcessQuery alias Helix.Server.Websocket.Channel.Server, as: ServerChannel @@ -48,36 +50,38 @@ defmodule Helix.Test.Features.Hack do ref = push socket, "cracker.bruteforce", params # Wait for response - assert_reply ref, :ok, response, timeout(:slow) - - # The response includes the Bruteforce process information - assert response.data.process_id + assert_reply ref, :ok, %{data: %{}}, timeout(:slow) # Wait for generic ProcessCreatedEvent - assert_push "event", _top_recalcado_event, timeout() - assert_push "event", process_created_event, timeout() - assert process_created_event.event == "process_created" + assert_push "event", _top_recalcado, timeout() + assert_push "event", process_created, timeout() + + assert process_created.event == "process_created" + + process_id = Process.ID.cast!(process_created.data.process_id) + connection_id = + Connection.ID.cast!(process_created.data.access.connection_id) # The BruteforceProcess is running as expected - process = ProcessQuery.fetch(response.data.process_id) + process = ProcessQuery.fetch(process_id) assert process - assert TunnelQuery.fetch_connection(response.data.access.connection_id) + assert TunnelQuery.fetch_connection(connection_id) # Let's cheat and finish the process right now TOPHelper.force_completion(process) # And soon we'll receive the PasswordAcquiredEvent - assert_push "event", password_acquired_event, timeout() - assert password_acquired_event.event == "server_password_acquired" + assert_push "event", password_acquired, timeout() + assert password_acquired.event == "server_password_acquired" # Which includes data about the server we've just hacked! - assert_id password_acquired_event.data.network_id, target_nip.network_id - assert password_acquired_event.data.server_ip == target_nip.ip - assert password_acquired_event.data.password + assert_id password_acquired.data.network_id, target_nip.network_id + assert password_acquired.data.server_ip == target_nip.ip + assert password_acquired.data.password # We'll receive the generic ProcessCompletedEvent - assert_push "event", process_conclusion_event, timeout() - assert process_conclusion_event.event == "process_completed" + assert_push "event", process_conclusion, timeout() + assert process_conclusion.event == "process_completed" db_server = DatabaseQuery.fetch_server( @@ -87,18 +91,17 @@ defmodule Helix.Test.Features.Hack do # The hacked server has been added to my Database assert db_server - assert db_server.password == password_acquired_event.data.password + assert db_server.password == password_acquired.data.password assert db_server.last_update > Utils.date_before(-1) # And I can actually login into the recently hacked server - gateway_ip = ServerHelper.get_ip(gateway) topic = ChannelHelper.server_topic_name(target_nip.network_id, target_nip.ip) params = %{ "gateway_ip" => gateway_ip, - "password" => password_acquired_event.data.password + "password" => password_acquired.data.password } {:ok, %{data: bootstrap}, new_socket} = @@ -113,6 +116,8 @@ defmodule Helix.Test.Features.Hack do assert bootstrap.filesystem assert bootstrap.logs assert bootstrap.processes + + TOPHelper.top_stop(gateway) end end diff --git a/test/features/process/lifecycle_test.exs b/test/features/process/lifecycle_test.exs index 25431a3b..09c486cf 100644 --- a/test/features/process/lifecycle_test.exs +++ b/test/features/process/lifecycle_test.exs @@ -35,18 +35,18 @@ defmodule Helix.Test.Features.Process.Lifecycle do # Starts the file download ref = push socket, "file.download", params - assert_reply ref, :ok, response, timeout(:slow) # The process was created - assert response.data.process_id - process_id = Process.ID.cast!(response.data.process_id) + assert response.data == %{} + + assert_push "event", top_recalcado_event, timeout() + assert_push "event", process_created_event, timeout() - assert_push "event", top_recalcado, timeout() - assert_push "event", process_created, timeout() + process_id = Process.ID.cast!(process_created_event.data.process_id) - assert top_recalcado.event == "top_recalcado" - assert process_created.event == "process_created" + assert top_recalcado_event.event == "top_recalcado" + assert process_created_event.event == "process_created" # Let's fetch the process, just to make sure process = ProcessQuery.fetch(process_id) @@ -75,8 +75,9 @@ defmodule Helix.Test.Features.Process.Lifecycle do # half: completing the process. We want to avoid using `force_completion` # from TOPHelper, so the completion is actually spontaneous. # In order to do that we create a very small process which needs to transfer - # a file of about ~1kb, taking less than a second. + # a file of about ~1kb, which takes less than a second. test "spontaneous completion" do + # TODO Agora dah # TODO: Local socket for local TOPREcalcado event {socket, %{gateway: gateway, destination: destination}} = ChannelSetup.join_server() diff --git a/test/features/process/recalque_test.exs b/test/features/process/recalque_test.exs index f8d34376..c945cdfe 100644 --- a/test/features/process/recalque_test.exs +++ b/test/features/process/recalque_test.exs @@ -19,6 +19,7 @@ defmodule Helix.Test.Features.Process.Recalque do @moduletag :feature + @relay nil @internet_id NetworkHelper.internet_id() describe "recalque" do @@ -55,7 +56,9 @@ defmodule Helix.Test.Features.Process.Recalque do # Create a download process assert {:ok, %{process_id: downloadA_id}} = - FilePublic.download(serverA, serverB, tunnelAB, storageA, dl_file) + FilePublic.download( + serverA, serverB, tunnelAB, storageA, dl_file, @relay + ) # Give some time for allocation # :timer.sleep(50) @@ -92,7 +95,9 @@ defmodule Helix.Test.Features.Process.Recalque do # Start the Bruteforce attack assert {:ok, %{process_id: bruteforce_id}} = - FilePublic.bruteforce(cracker, serverA, serverB, @internet_id, ipB, []) + FilePublic.bruteforce( + cracker, serverA, serverB, {@internet_id, ipB}, [], @relay + ) # Give some time for allocation # :timer.sleep(50) @@ -129,7 +134,9 @@ defmodule Helix.Test.Features.Process.Recalque do # will be recalculate on C and B. Then, it should recalculate A. assert {:ok, %{process_id: downloadC_id}} = - FilePublic.download(serverC, serverB, tunnelCB, storageC, dl_file) + FilePublic.download( + serverC, serverB, tunnelCB, storageC, dl_file, @relay + ) # :timer.sleep(50) diff --git a/test/process/event/process_created_test.exs b/test/process/event/process_created_test.exs index 74934969..d626e183 100644 --- a/test/process/event/process_created_test.exs +++ b/test/process/event/process_created_test.exs @@ -7,6 +7,7 @@ defmodule Helix.Process.Event.Process.CreatedTest do alias Helix.Test.Channel.Setup, as: ChannelSetup alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Process.View.Helper, as: ProcessViewHelper describe "Notificable.whom_to_notify/1" do test "servers are listed correctly" do @@ -19,24 +20,40 @@ defmodule Helix.Process.Event.Process.CreatedTest do describe "Notificable.generate_payload/2" do test "single server process create (player AT action_server)" do - socket = ChannelSetup.mock_server_socket([own_server: true]) + socket = ChannelSetup.mock_server_socket(own_server: true) gateway_id = socket.assigns.gateway.server_id + entity_id = socket.assigns.gateway.entity_id # Player doing an action on his own server - event = EventSetup.Process.created(gateway_id) + event = + EventSetup.Process.created( + gateway_id: gateway_id, + target_id: gateway_id, + entity_id: entity_id, + type: :bruteforce + ) assert {:ok, data} = Notificable.generate_payload(event, socket) - assert_payload_full(data) + ProcessViewHelper.assert_keys(data, :full) end test "multi server process create (attacker AT attack_source)" do socket = ChannelSetup.mock_server_socket() attack_source_id = socket.assigns.gateway.server_id + attacker_entity_id = socket.assigns.gateway.entity_id + attack_target_id = socket.assigns.destination.server_id - event = EventSetup.Process.created(attack_source_id, Server.ID.generate()) + # Simulate event between `attacker` and `victim` + event = + EventSetup.Process.created( + gateway_id: attack_source_id, + target_id: attack_target_id, + entity_id: attacker_entity_id, + type: :bruteforce + ) # Event originated on attack_source assert event.gateway_id == attack_source_id @@ -47,31 +64,52 @@ defmodule Helix.Process.Event.Process.CreatedTest do # Attacker has full access to the output payload assert {:ok, data} = Notificable.generate_payload(event, socket) - assert_payload_full(data) + ProcessViewHelper.assert_keys(data, :full) end test "multi server process create (attacker AT attack_target)" do socket = ChannelSetup.mock_server_socket() + attacker_entity_id = socket.assigns.gateway.entity_id attack_source_id = socket.assigns.gateway.server_id attack_target_id = socket.assigns.destination.server_id - event = EventSetup.Process.created(attack_source_id, attack_target_id) + # Simulate event between `attacker` and `victim` + event = + EventSetup.Process.created( + gateway_id: attack_source_id, + target_id: attack_target_id, + entity_id: attacker_entity_id, + type: :bruteforce + ) # Attacker has full access to the output payload assert {:ok, data} = Notificable.generate_payload(event, socket) - assert_payload_full(data) + ProcessViewHelper.assert_keys(data, :full) end test "multi server process create (third AT attack_source)" do + # `attacker` is doing some nasty stuff on someone socket = ChannelSetup.mock_server_socket() + attacker_entity_id = socket.assigns.gateway.entity_id + attack_source_id = socket.assigns.gateway.server_id - third_server_id = socket.assigns.gateway.server_id - attack_source_id = socket.assigns.destination.server_id - - # Action from `attack_source` to `attack_target` - event = EventSetup.Process.created(attack_source_id, Server.ID.generate()) + # Third is absolutely random to `attacker`. But `third` is connected to + # `attacker`. In the socket created below, `attacker` is the destination + # of `third`. + third_socket = + ChannelSetup.mock_server_socket(destination_id: attack_source_id) + third_server_id = third_socket.assigns.gateway.server_id + + # Simulate event/action from `attack_source` to `attack_target` + event = + EventSetup.Process.created( + gateway_id: attack_source_id, + target_id: Server.ID.generate(), + entity_id: attacker_entity_id, + type: :bruteforce + ) # Attack originated on `attack_source`, owned by `attacker` assert event.gateway_id == attack_source_id @@ -80,48 +118,40 @@ defmodule Helix.Process.Event.Process.CreatedTest do # And it targets `attack_target`, totally unrelated to `third` refute event.target_id == third_server_id - # `third` sees everything - assert {:ok, data} = Notificable.generate_payload(event, socket) + # Generates the payload as if `third` was receiving it + assert {:ok, data} = Notificable.generate_payload(event, third_socket) - # Third can see the full process, since it originated at `attack_source` - assert_payload_full(data) + # Third can see the full process, since the process originated at + # `attack_source` and `third` is connected to `attack_source`. + ProcessViewHelper.assert_keys(data, :full) end test "multi server process create (third AT attack_target)" do + # `victim` is being attacked by someone socket = ChannelSetup.mock_server_socket() + attacker_entity_id = socket.assigns.gateway.entity_id + attack_target_id = socket.assigns.destination.server_id - target_id = socket.assigns.destination.server_id - - # Action from `attack_source` to `attack_target` - event = EventSetup.Process.created(Server.ID.generate(), target_id) + # Third is absolutely random to `victim`. But `third` is connected to + # `victim`. In the socket created below, `victim` is the destination + # of `third`. + third_socket = + ChannelSetup.mock_server_socket(destination_id: attack_target_id) + + # Simulate event/action from random `attacker` targeting `victim`. + event = + EventSetup.Process.created( + gateway_id: Server.ID.generate(), + target_id: attack_target_id, + entity_id: attacker_entity_id, + type: :bruteforce + ) # `third` never gets the notification - assert {:ok, data} = Notificable.generate_payload(event, socket) + assert {:ok, data} = Notificable.generate_payload(event, third_socket) # Third-party can see the process exists, but not who created it. - assert_payload_censored(data) - end - - defp assert_payload_full(data) do - expected_keys = - [:process_id, :type, :network_id, :file_id, :source_ip, :target_ip, - :connection_id] - - Enum.each(expected_keys, fn key -> - assert Map.has_key?(data, key) - end) - end - - defp assert_payload_censored(data) do - expected_keys = [:process_id, :type, :network_id, :file_id, :target_ip] - rejected_keys = [:source_ip, :connection_id] - - Enum.each(expected_keys, fn key -> - assert Map.has_key?(data, key) - end) - Enum.each(rejected_keys, fn key -> - refute Map.has_key?(data, key) - end) + ProcessViewHelper.assert_keys(data, :partial) end end end diff --git a/test/server/websocket/channel/server/requests/cracker_test.exs b/test/server/websocket/channel/server/requests/cracker_test.exs index af95efea..6b1f73e3 100644 --- a/test/server/websocket/channel/server/requests/cracker_test.exs +++ b/test/server/websocket/channel/server/requests/cracker_test.exs @@ -6,7 +6,9 @@ defmodule Helix.Server.Websocket.Channel.Server.Requests.CrackerTest do import Helix.Test.Macros alias Helix.Cache.Query.Cache, as: CacheQuery + alias Helix.Network.Model.Connection alias Helix.Network.Query.Tunnel, as: TunnelQuery + alias Helix.Process.Model.Process alias Helix.Process.Query.Process, as: ProcessQuery alias Helix.Test.Channel.Setup, as: ChannelSetup @@ -37,22 +39,29 @@ defmodule Helix.Server.Websocket.Channel.Server.Requests.CrackerTest do ref = push socket, "cracker.bruteforce", params # Wait for response - assert_reply ref, :ok, response, timeout(:slow) + assert_reply ref, :ok, %{data: %{}}, timeout(:slow) + + assert_push "event", _top_recalcado, timeout() + assert_push "event", process_created_event, timeout() # All required fields are there - assert response.data.type == "cracker_bruteforce" - assert response.data.file - assert response.data.access.origin_id - assert response.data.access.priority - assert response.data.access.usage - assert response.data.network_id - assert response.data.state - assert response.data.progress - assert response.data.target_ip + assert process_created_event.data.type == "cracker_bruteforce" + assert process_created_event.data.file + assert process_created_event.data.access.origin_id + assert process_created_event.data.access.priority + assert process_created_event.data.access.usage + assert process_created_event.data.network_id + assert process_created_event.data.state + assert process_created_event.data.progress + assert process_created_event.data.target_ip + + process_id = Process.ID.cast!(process_created_event.data.process_id) + connection_id = + Connection.ID.cast!(process_created_event.data.access.connection_id) # It definitely worked. Yay! - assert ProcessQuery.fetch(response.data.process_id) - assert TunnelQuery.fetch_connection(response.data.access.connection_id) + assert ProcessQuery.fetch(process_id) + assert TunnelQuery.fetch_connection(connection_id) TOPHelper.top_stop(gateway) end diff --git a/test/server/websocket/channel/server/requests/pftp_test.exs b/test/server/websocket/channel/server/requests/pftp_test.exs index 53949911..d1fc8df8 100644 --- a/test/server/websocket/channel/server/requests/pftp_test.exs +++ b/test/server/websocket/channel/server/requests/pftp_test.exs @@ -110,12 +110,15 @@ defmodule Helix.Server.Websocket.Channel.Server.Requests.PFTPTest do ref = push socket, "pftp.file.download", params - assert_reply ref, :ok, %{data: process}, timeout(:slow) + assert_reply ref, :ok, %{}, timeout(:slow) - assert process.file.id == to_string(file.file_id) - assert process.type == "file_download" - assert process.data.connection_type == "public_ftp" - assert process.network_id == to_string(@internet_id) + assert_push "event", _top_recalcado_event, timeout() + assert_push "event", process_created_event, timeout() + + assert process_created_event.data.file.id == to_string(file.file_id) + assert process_created_event.data.type == "file_download" + assert process_created_event.data.data.connection_type == "public_ftp" + assert process_created_event.data.network_id == to_string(@internet_id) TOPHelper.top_stop(server) end diff --git a/test/software/action/flow/file/transfer_test.exs b/test/software/action/flow/file/transfer_test.exs index 66cbed98..18ede781 100644 --- a/test/software/action/flow/file/transfer_test.exs +++ b/test/software/action/flow/file/transfer_test.exs @@ -11,6 +11,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do alias Helix.Test.Software.Helper, as: SoftwareHelper alias Helix.Test.Software.Setup, as: SoftwareSetup + @relay nil + describe "transfer/4" do test "valid file download" do {gateway, _} = ServerSetup.server() @@ -22,8 +24,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do net = NetworkHelper.net() {:ok, process} = - FileTransferFlow.transfer( - :download, gateway, destination, file, destination_storage, net + FileTransferFlow.download( + gateway, destination, file, destination_storage, net, @relay ) # Generated process has the expected data @@ -40,8 +42,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do # Transferring again returns the same process (does not create a new one) {:ok, process2} = - FileTransferFlow.transfer( - :download, gateway, destination, file, destination_storage, net + FileTransferFlow.download( + gateway, destination, file, destination_storage, net, @relay ) assert process2.process_id == process.process_id @@ -59,8 +61,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do net = NetworkHelper.net() {:ok, process} = - FileTransferFlow.transfer( - :upload, gateway, destination, file, destination_storage, net + FileTransferFlow.upload( + gateway, destination, file, destination_storage, net, @relay ) # Generated process has the expected data @@ -77,8 +79,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do # Transferring again returns the same process (does not create a new one) {:ok, process2} = - FileTransferFlow.transfer( - :upload, gateway, destination, file, destination_storage, net + FileTransferFlow.upload( + gateway, destination, file, destination_storage, net, @relay ) assert process2.process_id == process.process_id @@ -96,8 +98,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do net = NetworkHelper.net() {:ok, process} = - FileTransferFlow.transfer( - :pftp_download, gateway, destination, file, destination_storage, net + FileTransferFlow.pftp_download( + gateway, destination, file, destination_storage, net, @relay ) # Generated process has the expected data @@ -114,8 +116,8 @@ defmodule Helix.Software.Action.Flow.File.TransferTest do # Transferring again returns the same process (does not create a new one) {:ok, process2} = - FileTransferFlow.transfer( - :pftp_download, gateway, destination, file, destination_storage, net + FileTransferFlow.pftp_download( + gateway, destination, file, destination_storage, net, @relay ) assert process2.process_id == process.process_id diff --git a/test/software/action/flow/file_test.exs b/test/software/action/flow/file_test.exs index a1b323d2..ac3eb904 100644 --- a/test/software/action/flow/file_test.exs +++ b/test/software/action/flow/file_test.exs @@ -3,12 +3,11 @@ defmodule Helix.Software.Action.Flow.FileTest do use Helix.Test.Case.Integration alias Helix.Cache.Query.Cache, as: CacheQuery - # alias Helix.Log.Action.Log, as: LogAction alias Helix.Software.Action.Flow.File, as: FileFlow + alias Helix.Server.Model.Server alias Helix.Test.Process.TOPHelper alias Helix.Test.Server.Setup, as: ServerSetup - # alias Helix.Test.Software.Helper, as: SoftwareHelper alias Helix.Test.Software.Setup, as: SoftwareSetup describe "execute_file/1" do @@ -16,7 +15,7 @@ defmodule Helix.Software.Action.Flow.FileTest do {file, _} = SoftwareSetup.non_executable_file() assert {:error, reason} = - FileFlow.execute_file(file, :wat, %{}, %{}, %{}, %{}) + FileFlow.execute_file({file, :wat}, %Server{}, %Server{}, %{}, %{}, nil) assert reason == :not_executable end @@ -76,7 +75,7 @@ defmodule Helix.Software.Action.Flow.FileTest do # Executes Cracker.bruteforce against the target server assert {:ok, _} = FileFlow.execute_file( - file, :bruteforce, source_server, target_server, params, meta + {file, :bruteforce}, source_server, target_server, params, meta, nil ) TOPHelper.top_stop(source_server) diff --git a/test/software/process/cracker/bruteforce_test.exs b/test/software/process/cracker/bruteforce_test.exs index cb938d86..941ba2ad 100644 --- a/test/software/process/cracker/bruteforce_test.exs +++ b/test/software/process/cracker/bruteforce_test.exs @@ -6,6 +6,7 @@ defmodule Helix.Software.Process.Cracker.BruteforceTest do alias Helix.Network.Query.Tunnel, as: TunnelQuery alias Helix.Process.Model.Processable alias Helix.Process.Public.View.Process, as: ProcessView + alias Helix.Software.Process.Cracker.Bruteforce, as: BruteforceProcess alias Helix.Test.Cache.Helper, as: CacheHelper alias Helix.Test.Process.Helper, as: ProcessHelper @@ -16,7 +17,7 @@ defmodule Helix.Software.Process.Cracker.BruteforceTest do alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Software.Setup, as: SoftwareSetup - alias Helix.Software.Process.Cracker.Bruteforce, as: BruteforceProcess + @relay nil describe "Process.Executable" do test "starts the bruteforce process when everything is OK" do @@ -40,7 +41,9 @@ defmodule Helix.Software.Process.Cracker.BruteforceTest do # Executes Cracker.bruteforce against the target server assert {:ok, process} = - BruteforceProcess.execute(source_server, target_server, params, meta) + BruteforceProcess.execute( + source_server, target_server, params, meta, @relay + ) # Process data is correct assert process.connection_id diff --git a/test/software/public/index_test.exs b/test/software/public/index_test.exs index 67882187..fb4cccd2 100644 --- a/test/software/public/index_test.exs +++ b/test/software/public/index_test.exs @@ -8,6 +8,7 @@ defmodule Helix.Software.Public.IndexTest do alias Helix.Test.Software.Setup, as: SoftwareSetup describe "index/1" do + # TODO: Test it hides hidden/encrypted files, etc. test "indexes correctly" do {server, _} = ServerSetup.server() @@ -21,15 +22,9 @@ defmodule Helix.Software.Public.IndexTest do result_file2 = Enum.find(index[file2.path], &(find_by_id(&1, file2))) result_file3 = Enum.find(index[file3.path], &(find_by_id(&1, file3))) - assert result_file1.path == file1.full_path - assert result_file1.size == file1.file_size - assert result_file1.software_type == file1.software_type - - # TODO: This function is not testing modules - # TODO: Test it hides hidden/encrypted files, etc. - - assert result_file2.path == file2.full_path - assert result_file3.path == file3.full_path + assert result_file1 == file1 + assert result_file2 == file2 + assert result_file3 == file3 end end diff --git a/test/software/public/pftp_test.exs b/test/software/public/pftp_test.exs index a084426a..bc8307a0 100644 --- a/test/software/public/pftp_test.exs +++ b/test/software/public/pftp_test.exs @@ -10,6 +10,8 @@ defmodule Helix.Software.Public.PFTPTest do alias Helix.Test.Software.Helper, as: SoftwareHelper alias Helix.Test.Software.Setup, as: SoftwareSetup + @relay nil + describe "download/4" do test "starts a pftp download process" do {gateway, _} = ServerSetup.server() @@ -20,7 +22,7 @@ defmodule Helix.Software.Public.PFTPTest do storage = SoftwareHelper.get_storage(pftp.server_id) assert {:ok, process} = - PFTPPublic.download(gateway, destination, storage, file) + PFTPPublic.download(gateway, destination, storage, file, @relay) assert process.gateway_id == gateway.server_id assert process.target_id == pftp.server_id diff --git a/test/software/public/software_test.exs b/test/software/public/software_test.exs index ea17e62c..4e8863c9 100644 --- a/test/software/public/software_test.exs +++ b/test/software/public/software_test.exs @@ -12,6 +12,8 @@ defmodule Helix.Software.Public.FileTest do alias Helix.Test.Software.Helper, as: SoftwareHelper alias Helix.Test.Software.Setup, as: SoftwareSetup + @relay nil + describe "bruteforce/6" do test "starts a bruteforce attack" do {source_server, %{entity: source_entity}} = ServerSetup.server() @@ -29,9 +31,9 @@ defmodule Helix.Software.Public.FileTest do cracker, source_server, target_server, - target_nip.network_id, - target_nip.ip, - [] + {target_nip.network_id, target_nip.ip}, + [], + @relay ) assert process.connection_id @@ -63,7 +65,7 @@ defmodule Helix.Software.Public.FileTest do storage = SoftwareHelper.get_storage(destination) assert {:ok, process} = - FilePublic.download(gateway, destination, tunnel, storage, file) + FilePublic.download(gateway, destination, tunnel, storage, file, @relay) assert process.file_id == file.file_id assert process.type == :file_download diff --git a/test/software/websocket/requests/file/download_test.exs b/test/software/websocket/requests/file/download_test.exs index daf550b9..54736285 100644 --- a/test/software/websocket/requests/file/download_test.exs +++ b/test/software/websocket/requests/file/download_test.exs @@ -127,12 +127,7 @@ defmodule Helix.Software.Websocket.Requests.File.DownloadTest do request = RequestHelper.mock_request(FileDownloadRequest, params) {:ok, request} = Requestable.check_permissions(request, socket) - assert {:ok, request} = Requestable.handle_request(request, socket) - - assert request.meta.process.process_id - assert request.meta.process.file_id == file.file_id - assert request.meta.process.gateway_id == gateway.server_id - assert request.meta.process.target_id == destination.server_id + assert {:ok, _request} = Requestable.handle_request(request, socket) TOPHelper.top_stop(gateway) end diff --git a/test/support/channel/request/helper.ex b/test/support/channel/request/helper.ex index 441a4185..27549b85 100644 --- a/test/support/channel/request/helper.ex +++ b/test/support/channel/request/helper.ex @@ -4,7 +4,8 @@ defmodule Helix.Test.Channel.Request.Helper do %{ __struct__: module, params: params, - meta: meta + meta: meta, + relay: Helix.Websocket.Request.Relay.new(params) } end end diff --git a/test/support/channel/setup.ex b/test/support/channel/setup.ex index 035b4736..d00e5085 100644 --- a/test/support/channel/setup.ex +++ b/test/support/channel/setup.ex @@ -53,7 +53,12 @@ defmodule Helix.Test.Channel.Setup do {token, _} = AccountSetup.token([account: account]) {:ok, socket} = connect(Websocket, %{token: token}) - {socket, Map.merge(%{account: account}, related)} + related = + related + |> Map.merge(%{account: account}) + |> Map.merge(%{token: token}) + + {socket, related} end @doc """ @@ -73,13 +78,13 @@ defmodule Helix.Test.Channel.Setup do raise "You must specify both :account_id and :socket" end - {socket, account_id} = + {socket, account_id, socket_related} = if opts[:socket] do - {opts[:socket], opts[:account_id]} + {opts[:socket], opts[:account_id], %{}} else - {socket, %{account: account}} = create_socket() + {socket, related} = create_socket() - {socket, account.account_id} + {socket, related.account.account_id, related} end topic = "account:" <> to_string(account_id) @@ -90,6 +95,7 @@ defmodule Helix.Test.Channel.Setup do account_id: account_id, entity_id: Entity.ID.cast!(to_string(account_id)) } + |> Map.merge(socket_related) {socket, related} end @@ -117,7 +123,14 @@ defmodule Helix.Test.Channel.Setup do destination_ip :: Network.ip | nil """ def join_server(opts \\ []) do - {socket, %{account: account, server: gateway}} = create_socket() + {socket, %{account: account, server: gateway}} = + if opts[:socket] do + gateway = opts[:socket].assigns.gateway.server_id |> ServerQuery.fetch() + + {opts[:socket], %{account: nil, server: gateway}} + else + create_socket() + end local? = Keyword.get(opts, :own_server, false) diff --git a/test/support/event/setup/process.ex b/test/support/event/setup/process.ex index 17753f4d..e8321733 100644 --- a/test/support/event/setup/process.ex +++ b/test/support/event/setup/process.ex @@ -1,27 +1,18 @@ defmodule Helix.Test.Event.Setup.Process do alias Helix.Process.Event.Process.Created, as: ProcessCreatedEvent - alias Helix.Server.Model.Server alias HELL.TestHelper.Random alias Helix.Test.Process.Setup, as: ProcessSetup - def created, - do: created(Server.ID.generate(), Server.ID.generate()) - - def created(gateway_id), - do: created(gateway_id, gateway_id) - - def created(gateway_id, target_id) do - # Generates a random process on the given server(s) - process_opts = [gateway_id: gateway_id, target_id: target_id] - {process, _} = ProcessSetup.fake_process(process_opts) + def created(opts \\ []) do + {process, _} = ProcessSetup.fake_process(opts) %ProcessCreatedEvent{ confirmed: true, process: process, - gateway_id: gateway_id, - target_id: target_id, + gateway_id: process.gateway_id, + target_id: process.target_id, gateway_ip: Random.ipv4(), target_ip: Random.ipv4() } diff --git a/test/support/process/helper/top.ex b/test/support/process/helper/top.ex index d598c4fe..b47cc4ec 100644 --- a/test/support/process/helper/top.ex +++ b/test/support/process/helper/top.ex @@ -19,12 +19,13 @@ defmodule Helix.Test.Process.TOPHelper do @doc """ Completes the process, emitting the related events and removing from the db. """ - def force_completion(process_id = %Process.ID{}) do + def force_completion(process_idt, opts \\ []) + def force_completion(process_id = %Process.ID{}, opts) do process_id |> ProcessQuery.fetch() - |> force_completion() + |> force_completion(opts) end - def force_completion(process = %Process{}) do + def force_completion(process = %Process{}, opts) do # Update the DB process entry, now it has magically reached its objective process |> Changeset.change() @@ -33,7 +34,11 @@ defmodule Helix.Test.Process.TOPHelper do |> ProcessRepo.update() # Force a recalque on the server - TOPAction.recalque(process) + if opts[:from] do + TOPAction.recalque(process, opts[:from]) + else + TOPAction.recalque(process) + end end @doc """ diff --git a/test/support/software/flow.ex b/test/support/software/flow.ex index 8e0cadd1..37bcf040 100644 --- a/test/support/software/flow.ex +++ b/test/support/software/flow.ex @@ -42,7 +42,7 @@ defmodule Helix.Test.Software.Setup.Flow do } {:ok, process} = - FileTransferProcess.execute(gateway, destination, params, meta) + FileTransferProcess.execute(gateway, destination, params, meta, nil) {process, %{}} end @@ -77,7 +77,7 @@ defmodule Helix.Test.Software.Setup.Flow do } {:ok, process} = - BruteforceProcess.execute(source_server, target_server, params, meta) + BruteforceProcess.execute(source_server, target_server, params, meta, nil) related = %{ source_server: source_server, diff --git a/test/support/universe/bank/setup.ex b/test/support/universe/bank/setup.ex index b41da96f..b42fb547 100644 --- a/test/support/universe/bank/setup.ex +++ b/test/support/universe/bank/setup.ex @@ -224,7 +224,7 @@ defmodule Helix.Test.Universe.Bank.Setup do net = NetworkHelper.net() {:ok, process} = - BankTransferFlow.start(acc1, acc2, amount, player, gateway, net) + BankTransferFlow.start(acc1, acc2, amount, player, gateway, net, nil) related = %{ acc1: acc1, diff --git a/test/universe/bank/action/flow/bank_account_test.exs b/test/universe/bank/action/flow/bank_account_test.exs index e1252ec4..67a0a04e 100644 --- a/test/universe/bank/action/flow/bank_account_test.exs +++ b/test/universe/bank/action/flow/bank_account_test.exs @@ -14,6 +14,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do alias Helix.Test.Process.TOPHelper alias Helix.Test.Server.Setup, as: ServerSetup + @relay nil + describe "reveal_password/4" do @tag :slow test "default life cycle" do @@ -30,10 +32,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do # Create process to reveal password {:ok, process} = BankAccountFlow.reveal_password( - acc, - token.token_id, - gateway, - atm + acc, token.token_id, gateway, atm, @relay ) # Ensure process is valid diff --git a/test/universe/bank/action/flow/bank_transfer_test.exs b/test/universe/bank/action/flow/bank_transfer_test.exs index 687cbf79..317bd399 100644 --- a/test/universe/bank/action/flow/bank_transfer_test.exs +++ b/test/universe/bank/action/flow/bank_transfer_test.exs @@ -13,6 +13,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransferTest do alias Helix.Test.Process.TOPHelper alias Helix.Test.Universe.Bank.Setup, as: BankSetup + @relay nil + describe "start/1" do @tag :slow test "default life cycle (different atms)" do @@ -25,7 +27,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransferTest do # They see me flowin', they hatin' {:ok, process} = - BankTransferFlow.start(acc1, acc2, amount, player, gateway, net) + BankTransferFlow.start(acc1, acc2, amount, player, gateway, net, @relay) transfer_id = process.data.transfer_id # Transfer was added to the DB @@ -77,7 +79,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransferTest do net = NetworkHelper.net() {:ok, process} = - BankTransferFlow.start(acc1, acc2, amount, player, gateway, net) + BankTransferFlow.start(acc1, acc2, amount, player, gateway, net, @relay) # Get connection data connection = TunnelQuery.fetch_connection(process.connection_id) diff --git a/test/universe/bank/event/handler/bank/transfer_test.exs b/test/universe/bank/event/handler/bank/transfer_test.exs index 6bce8e4a..19f052b7 100644 --- a/test/universe/bank/event/handler/bank/transfer_test.exs +++ b/test/universe/bank/event/handler/bank/transfer_test.exs @@ -13,6 +13,8 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.TransferTest do alias Helix.Test.Process.TOPHelper alias Helix.Test.Universe.Bank.Setup, as: BankSetup + @relay nil + describe "transfer_aborted/1" do test "life cycle" do amount = 100_000_000 @@ -23,7 +25,7 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.TransferTest do net = NetworkHelper.net() {:ok, process} = - BankTransferFlow.start(acc1, acc2, amount, player, gateway, net) + BankTransferFlow.start(acc1, acc2, amount, player, gateway, net, @relay) transfer_id = process.data.transfer_id assert ProcessQuery.fetch(process) From 2e76ba4aac3908f798ced95d180649d3f3859a5f Mon Sep 17 00:00:00 2001 From: Renato Massaro Date: Fri, 10 Nov 2017 01:40:12 -0200 Subject: [PATCH 2/2] Better organization of Websocket modules --- lib/server/websocket/channel/server.ex | 6 ++--- lib/software/public/index.ex | 2 ++ lib/websocket/{ => join}/join.ex | 0 lib/websocket/{ => join}/joinable.ex | 0 lib/websocket/request/relay.ex | 27 ++++++++++++++++++++ lib/websocket/{ => request}/request.ex | 29 ---------------------- lib/websocket/{ => request}/requestable.ex | 0 7 files changed, 32 insertions(+), 32 deletions(-) rename lib/websocket/{ => join}/join.ex (100%) rename lib/websocket/{ => join}/joinable.ex (100%) create mode 100644 lib/websocket/request/relay.ex rename lib/websocket/{ => request}/request.ex (81%) rename lib/websocket/{ => request}/requestable.ex (100%) diff --git a/lib/server/websocket/channel/server.ex b/lib/server/websocket/channel/server.ex index b2daeff1..3ee6fda1 100644 --- a/lib/server/websocket/channel/server.ex +++ b/lib/server/websocket/channel/server.ex @@ -85,7 +85,7 @@ channel Helix.Server.Websocket.Channel.Server do - storage_id: Specify which storage the file should be downloaded to. Defaults to the main storage. - Returns: RenderedProcess.t + Returns: :ok Errors: - "file_not_found": Requested file to be downloaded was not found @@ -161,7 +161,7 @@ channel Helix.Server.Websocket.Channel.Server do - storage_id: Specify which storage the file should be downloaded to. Defaults to the main storage. - Returns: RenderedProcess.t + Returns: :ok Errors: - "pftp_must_be_local": PFTP operations must happen at the local socket. @@ -205,7 +205,7 @@ channel Helix.Server.Websocket.Channel.Server do Note that all bruteforce attacks must originate from a server owned by the entity starting the attack. - Returns: RenderedProcess.t + Returns: :ok Errors: - "cracker_not_found" - Player attempting the attack does not have a valid diff --git a/lib/software/public/index.ex b/lib/software/public/index.ex index 194fe4b5..8e22cb37 100644 --- a/lib/software/public/index.ex +++ b/lib/software/public/index.ex @@ -53,6 +53,8 @@ defmodule Helix.Software.Public.Index do end) end + @spec render_file(File.t) :: + rendered_index_file def render_file(file = %File{}) do %{ file_id: to_string(file.file_id), diff --git a/lib/websocket/join.ex b/lib/websocket/join/join.ex similarity index 100% rename from lib/websocket/join.ex rename to lib/websocket/join/join.ex diff --git a/lib/websocket/joinable.ex b/lib/websocket/join/joinable.ex similarity index 100% rename from lib/websocket/joinable.ex rename to lib/websocket/join/joinable.ex diff --git a/lib/websocket/request/relay.ex b/lib/websocket/request/relay.ex new file mode 100644 index 00000000..aabdf2ef --- /dev/null +++ b/lib/websocket/request/relay.ex @@ -0,0 +1,27 @@ +defmodule Helix.Websocket.Request.Relay do + @moduledoc """ + `RequestRelay` is a struct intended to be relayed all the way from the Request + to the Public to the ActionFlow, so it can be used by `Helix.Event` to + identify the `request_id` and create a meaningful stacktrace. + """ + + defstruct [:request_id] + + @type t :: t_of_type(binary) + + @type t_of_type(type) :: + %__MODULE__{ + request_id: type + } + + @spec new(map) :: + t + | t_of_type(nil) + def new(%{"request_id" => request_id}) when is_binary(request_id) do + %__MODULE__{ + request_id: request_id + } + end + def new(_), + do: %__MODULE__{} +end diff --git a/lib/websocket/request.ex b/lib/websocket/request/request.ex similarity index 81% rename from lib/websocket/request.ex rename to lib/websocket/request/request.ex index 084f05ec..1a0c5657 100644 --- a/lib/websocket/request.ex +++ b/lib/websocket/request/request.ex @@ -115,33 +115,4 @@ defmodule Helix.Websocket.Request do end end - -end - -defmodule Helix.Websocket.Request.Relay do - @moduledoc """ - `RequestRelay` is a struct intended to be relayed all the way from the Request - to the Public to the ActionFlow, so it can be used by `Helix.Event` to - identify the `request_id` and create a meaningful stacktrace. - """ - - defstruct [:request_id] - - @type t :: t_of_type(binary) - - @type t_of_type(type) :: - %__MODULE__{ - request_id: type - } - - @spec new(map) :: - t - | t_of_type(nil) - def new(%{"request_id" => request_id}) when is_binary(request_id) do - %__MODULE__{ - request_id: request_id - } - end - def new(_), - do: %__MODULE__{} end diff --git a/lib/websocket/requestable.ex b/lib/websocket/request/requestable.ex similarity index 100% rename from lib/websocket/requestable.ex rename to lib/websocket/request/requestable.ex