Skip to content
This repository was archived by the owner on Jun 11, 2023. It is now read-only.

Commit a1404e3

Browse files
committed
WIP LogRecoverRequest
1 parent 241fb6c commit a1404e3

File tree

12 files changed

+884
-6
lines changed

12 files changed

+884
-6
lines changed

lib/log/action/flow/recover.ex

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
2+
defmodule Helix.Log.Action.Flow.Recover do
3+
4+
alias Helix.Event
5+
alias Helix.Entity.Model.Entity
6+
alias Helix.Network.Model.Connection
7+
alias Helix.Network.Model.Tunnel
8+
alias Helix.Server.Model.Server
9+
alias Helix.Software.Model.File
10+
alias Helix.Log.Model.Log
11+
12+
alias Helix.Log.Process.Recover, as: LogRecoverProcess
13+
14+
@spec global(
15+
Server.t,
16+
Server.t,
17+
File.t,
18+
Entity.id,
19+
{Tunnel.t, Connection.ssh} | nil,
20+
Event.relay
21+
) ::
22+
term
23+
def global(
24+
gateway = %Server{},
25+
endpoint = %Server{},
26+
recover = %File{software_type: :log_recover},
27+
entity_id = %Entity.ID{},
28+
conn,
29+
relay
30+
) do
31+
start_process(gateway, endpoint, nil, recover, entity_id, conn, relay)
32+
end
33+
34+
@spec custom(
35+
Server.t,
36+
Server.t,
37+
Log.t,
38+
File.t,
39+
Entity.id,
40+
{Tunnel.t, Connection.ssh} | nil,
41+
Event.relay
42+
) ::
43+
term
44+
def custom(
45+
gateway = %Server{},
46+
endpoint = %Server{},
47+
log = %Log{},
48+
recover = %File{software_type: :log_recover},
49+
entity_id = %Entity.ID{},
50+
conn,
51+
relay
52+
) do
53+
start_process(
54+
gateway, endpoint, log, recover, entity_id, conn, relay
55+
)
56+
end
57+
58+
defp start_process(
59+
gateway = %Server{},
60+
endpoint = %Server{},
61+
log,
62+
recover = %File{software_type: :log_recover},
63+
entity_id = %Entity.ID{},
64+
conn_info,
65+
relay
66+
) do
67+
method =
68+
if is_nil(log) do
69+
:global
70+
else
71+
:custom
72+
end
73+
74+
{network_id, ssh} =
75+
if is_nil(conn_info) do
76+
{nil, nil}
77+
else
78+
{tunnel, ssh} = conn_info
79+
{tunnel.network_id, ssh}
80+
end
81+
82+
params = %{}
83+
84+
meta =
85+
%{
86+
recover: recover,
87+
log: log,
88+
method: method,
89+
ssh: ssh,
90+
entity_id: entity_id,
91+
network_id: network_id
92+
}
93+
94+
LogRecoverProcess.execute(gateway, endpoint, params, meta, relay)
95+
end
96+
end

lib/log/event/log.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ defmodule Helix.Log.Event.Log do
145145

146146
publish do
147147

148+
alias Helix.Log.Public.Index, as: LogIndex
149+
148150
@event :log_recovered
149151

150152
def generate_payload(event, _socket) do
151-
data = %{
152-
log_id: to_string(event.log.log_id)
153-
}
153+
data = LogIndex.render_log(event.log)
154154

155155
{:ok, data}
156156
end

lib/log/henforcer/log/recover.ex

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
defmodule Helix.Log.Henforcer.Log.Recover do
2+
3+
import Helix.Henforcer
4+
5+
alias Helix.Server.Henforcer.Server, as: ServerHenforcer
6+
alias Helix.Server.Model.Server
7+
alias Helix.Software.Henforcer.File, as: FileHenforcer
8+
alias Helix.Software.Model.File
9+
alias Helix.Log.Henforcer.Log, as: LogHenforcer
10+
alias Helix.Log.Model.Log
11+
12+
@type can_recover_global_relay :: %{gateway: Server.t, recover: File.t}
13+
@type can_recover_global_relay_partial :: map
14+
@type can_recover_global_error ::
15+
ServerHenforcer.server_exists_error
16+
| exists_recover_error
17+
18+
@spec can_recover_global?(Server.id) ::
19+
{true, can_recover_global_relay}
20+
| can_recover_global_error
21+
@doc """
22+
Henforces that the player can start a global LogRecover process.
23+
24+
In order to recover globally, all a user needs to have is:
25+
- SSH access to the server
26+
- a valid LogRecover file on his gateway filesystem
27+
"""
28+
def can_recover_global?(gateway_id) do
29+
with \
30+
{true, r1} <- ServerHenforcer.server_exists?(gateway_id),
31+
r1 = replace(r1, :server, :gateway),
32+
gateway = r1.gateway,
33+
{true, r2} <- exists_recover?(gateway),
34+
r2 = replace(r2, :file, :recover, only: true)
35+
do
36+
[r1, r2]
37+
|> relay()
38+
|> reply_ok()
39+
end
40+
end
41+
42+
@type can_recover_custom_relay ::
43+
%{log: Log.t, gateway: Server.t, recover: File.t}
44+
@type can_recover_custom_relay_partial :: map
45+
@type can_recover_custom_error ::
46+
LogHenforcer.log_exists_error
47+
| LogHenforcer.belongs_to_server_error
48+
| ServerHenforcer.server_exists_error
49+
| exists_recover_error
50+
51+
@spec can_recover_custom?(Log.id, Server.id, Server.id) ::
52+
{true, can_recover_custom_relay}
53+
| can_recover_custom_error
54+
@doc """
55+
Henforces that the player can start a custom LogRecover process.
56+
57+
In order to recover a specific (custom) log, the player must have the same
58+
requirements of a `global` recover (SSH access and valid LogRecover file), and
59+
also the given `log_id` must exist, and it must exist on the target server.
60+
"""
61+
def can_recover_custom?(log_id = %Log.ID{}, gateway_id, target_id) do
62+
with \
63+
{true, r1} <- LogHenforcer.log_exists?(log_id),
64+
log = r1.log,
65+
66+
{true, _} <- LogHenforcer.belongs_to_server?(log, target_id),
67+
68+
{true, r2} <- ServerHenforcer.server_exists?(gateway_id),
69+
r2 = replace(r2, :server, :gateway),
70+
gateway = r2.gateway,
71+
72+
{true, r3} <- exists_recover?(gateway),
73+
r3 = replace(r3, :file, :recover, only: true)
74+
do
75+
[r1, r2, r3]
76+
|> relay()
77+
|> reply_ok()
78+
end
79+
end
80+
81+
@type exists_recover_relay :: FileHenforcer.exists_software_module_relay
82+
@type exists_recover_relay_partial ::
83+
FileHenforcer.exists_software_module_relay_partial
84+
@type exists_recover_error ::
85+
{false, {:recover, :not_found}, exists_recover_relay_partial}
86+
87+
@spec exists_recover?(Server.t) ::
88+
{true, exists_recover_relay}
89+
| exists_recover_error
90+
@doc """
91+
Ensures that exists a Recover file on `server`, sorting the result by `module`
92+
(only `:log_recover` in this context).
93+
94+
It's simply a wrapper over `FileHenforcer.exists_software_module?` used to
95+
generate a more meaningful error message ("recover_not_found") instead of
96+
"module_not_found".
97+
"""
98+
def exists_recover?(server = %Server{}) do
99+
henforce_else(
100+
FileHenforcer.exists_software_module?(:log_recover, server),
101+
{:recover, :not_found}
102+
)
103+
end
104+
end

lib/log/process/recover.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ process Helix.Log.Process.Recover do
231231
if f.log.revisions.extra == 0 do
232232
999_999_999_999
233233
else
234-
(f.log.revisions.total * multiplier) / f.recover.version.log_recover
234+
((f.log.revisions.total * multiplier) / f.recover.version.log_recover) + 5000
235235
end
236236
end
237237

lib/log/public/recover.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule Helix.Log.Public.Recover do
2+
3+
alias Helix.Log.Action.Flow.Recover, as: RecoverFlow
4+
5+
defdelegate global(gateway, endpoint, recover, entity, conn_info, relay),
6+
to: RecoverFlow
7+
8+
defdelegate custom(gateway, endpoint, log, recover, entity, conn_info, relay),
9+
to: RecoverFlow
10+
end
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import Helix.Websocket.Request
2+
3+
request Helix.Log.Websocket.Requests.Recover do
4+
@moduledoc """
5+
`LogRecoverRequest` is called when the player wants to recover a log. It may
6+
either be a `global` recovery, in which case a recoverable log is randomly
7+
selected from all logs within the server, or it may be a `custom` recovery,
8+
in which case a specific log to be recovered is defined by the player.
9+
"""
10+
11+
import HELL.Macros
12+
13+
alias Helix.Server.Query.Server, as: ServerQuery
14+
alias Helix.Log.Henforcer.Log.Recover, as: LogRecoverHenforcer
15+
alias Helix.Log.Model.Log
16+
alias Helix.Log.Public.Recover, as: RecoverPublic
17+
18+
def check_params(request, socket) do
19+
case request.unsafe["method"] do
20+
"global" ->
21+
check_params_global(request, socket)
22+
23+
"custom" ->
24+
check_params_custom(request, socket)
25+
26+
_ ->
27+
reply_error(request, "bad_method")
28+
end
29+
end
30+
31+
defp check_params_global(request, _socket),
32+
do: update_params(request, %{method: :global}, reply: true)
33+
34+
defp check_params_custom(request, _socket) do
35+
with \
36+
{:ok, log_id} <- Log.ID.cast(request.unsafe["log_id"])
37+
do
38+
params = %{method: :custom, log_id: log_id}
39+
40+
update_params(request, params, reply: true)
41+
else
42+
_ ->
43+
bad_request(request)
44+
end
45+
end
46+
47+
def check_permissions(request = %{params: %{method: :global}}, socket) do
48+
gateway_id = socket.assigns.gateway.server_id
49+
50+
case LogRecoverHenforcer.can_recover_global?(gateway_id) do
51+
{true, relay} ->
52+
meta = %{gateway: relay.gateway, recover: relay.recover}
53+
update_meta(request, meta, reply: true)
54+
55+
{false, reason, _} ->
56+
reply_error(request, reason)
57+
end
58+
end
59+
60+
def check_permissions(request = %{params: %{method: :custom}}, socket) do
61+
log_id = request.params.log_id
62+
gateway_id = socket.assigns.gateway.server_id
63+
target_id = socket.assigns.destination.server_id
64+
65+
can_recover? =
66+
LogRecoverHenforcer.can_recover_custom?(log_id, gateway_id, target_id)
67+
68+
case can_recover? do
69+
{true, relay} ->
70+
meta = %{gateway: relay.gateway, recover: relay.recover, log: relay.log}
71+
update_meta(request, meta, reply: true)
72+
73+
{false, reason, _} ->
74+
reply_error(request, reason)
75+
end
76+
end
77+
78+
def handle_request(request, socket) do
79+
entity_id = socket.assigns.entity_id
80+
recover = request.meta.recover
81+
gateway = request.meta.gateway
82+
relay = request.relay
83+
84+
{target, conn_info} =
85+
if socket.assigns.meta.access == :local do
86+
{gateway, nil}
87+
else
88+
{
89+
ServerQuery.fetch(socket.assigns.destination.server_id),
90+
{socket.assigns.tunnel, socket.assigns.ssh}
91+
}
92+
end
93+
94+
hespawn fn ->
95+
if request.params.method == :global do
96+
RecoverPublic.global(
97+
gateway, target, recover, entity_id, conn_info, relay
98+
)
99+
else
100+
log = request.meta.log
101+
102+
RecoverPublic.custom(
103+
gateway, target, log, recover, entity_id, conn_info, relay
104+
)
105+
end
106+
end
107+
108+
reply_ok(request)
109+
end
110+
111+
render_empty()
112+
end

0 commit comments

Comments
 (0)