Skip to content

Commit

Permalink
Device certificate fixes (#1539)
Browse files Browse the repository at this point in the history
* Fix bugs where device certificates cannot be uploaded or downloaded

- Fix broken certificate download by preloading device certificates in plug
- Fix certificate upload by adding phx-change handler to live upload

* Add tests for certificate upload and download

* Add tests for deleting and downloading certificates

---------

Co-authored-by: Josh Kalderimis <josh.kalderimis@gmail.com>
  • Loading branch information
elinol and joshk authored Sep 24, 2024
1 parent 80f7e3d commit a17a80a
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/nerves_hub_web/live/devices/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ defmodule NervesHubWeb.Live.Devices.Settings do
{:noreply, assign(socket, :toggle_upload, toggle != "true")}
end

# A phx-change handler is required when using live uploads.
def handle_event("validate-cert", _, socket), do: {:noreply, socket}

def handle_event(
"delete-certificate",
%{"serial" => serial},
Expand Down
2 changes: 1 addition & 1 deletion lib/nerves_hub_web/live/devices/settings.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
<span class="action-text">Cancel Upload</span>
</button>
<% else %>
<button class="btn btn-outline-light btn-action" type="button" phx-click="toggle-upload" phx-value-toggle={to_string(@toggle_upload)}>
<button id="toggle-certificate-upload" class="btn btn-outline-light btn-action" type="button" phx-click="toggle-upload" phx-value-toggle={to_string(@toggle_upload)}>
<div class="button-icon add"></div>
</button>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion lib/nerves_hub_web/plugs/device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule NervesHubWeb.Plugs.Device do
%{params: %{"identifier" => device_identifier}, assigns: %{org: org}} = conn,
_opts
) do
case Devices.get_device_by_identifier(org, device_identifier) do
case Devices.get_device_by_identifier(org, device_identifier, :device_certificates) do
{:ok, device} ->
assign(conn, :device, device)

Expand Down
33 changes: 33 additions & 0 deletions test/fixtures/ssl/device-test-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIUHqiYsi4e8UyttoXk2/xDsaYXZq8wDQYJKoZIhvcNAQEL
BQAwbTELMAkGA1UEBhMCU0UxDTALBgNVBAgMBHRlc3QxDTALBgNVBAcMBHRlc3Qx
DTALBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxDTALBgNVBAMMBHRlc3QxEzAR
BgkqhkiG9w0BCQEWBHRlc3QwHhcNMjQwOTI0MDgwMDA4WhcNMzgwNjAzMDgwMDA4
WjBtMQswCQYDVQQGEwJTRTENMAsGA1UECAwEdGVzdDENMAsGA1UEBwwEdGVzdDEN
MAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDENMAsGA1UEAwwEdGVzdDETMBEG
CSqGSIb3DQEJARYEdGVzdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AOrzg8v6XN+77/izOq36e97xGlsGEFOxyzHxkJqvuJ90QPliNtdkB910B0rC8pXh
1i62izdvAoaOr9XfN0qFtv+RQ6wMh0ynxpOykVtIIszt+x/pGhEw1eI15IvPHlSv
sFlEkRaZj6tEzqk5DV23xQwrzJBDmEICvhHVfNYNEnqKCq07hgH1/cP2LhjO30Z6
/BXv7X4EnwtNa2aVGhbZDzIeQ6gAduW2wiPcIJeG0UprJlwQeR27A/Cqo+mSgptm
q/70gI6qnYMiwMpCrCNwr4OsFtW4/6JwhYar09dnlrvoxh0C6XLmhaUBYJoRL8JT
S569FKEgILHOJQOEwGkCfYR1o/26sTKsclBHRHHNVhTL5VgxxS432wepgf9zdUFQ
drsU6PpO3FoNQszGiFZ5cXZceN1SGyIWzFZy93DGu1RtE+cq43WtiO/cgdV2JMNU
b34w781UA6/2GCPSyniHC68yyk3yyP3KIveI58ZMbf1ESfE3H7cL7RM0oHA+1YjU
oHSpnkcGoD0PjwuHLpQ3uPCmHtbaAcYTMT9xBJ8WFRbrdvBMnexZyo19c/nmlDF0
V5qCIqvz8Gg1WXscSfdj7l35Cd69xEsECaXAN0NLYxcFmi4mCT5WNUJJeQmnnH6z
JsjG8SjcEQpnb9H7R/+gvGerHQVdd3meOTInRjXpPoXpAgMBAAGjUzBRMB0GA1Ud
DgQWBBS3QBAdnHFtPWw+uUHj5WjNkM9kUTAfBgNVHSMEGDAWgBS3QBAdnHFtPWw+
uUHj5WjNkM9kUTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCc
AVS9X5QcMheM2iRGK786z+kSGZ1iIu6yivlSgR9uWmjqhkUYntNgN/qIbzsXGCZX
bk5diJATZFxXQbVSCBZid0s/pJ0KJjWpu9KhcZahFfGl+dMb+vaHxUzq5Q2bB17h
FBucULNbLYD+QiMDNc/btmMgjYpX6nuDFFhRdSz8tfAyc5LzJxKG02Wbgq/yL9qE
P217KH/gDWmpXRXw119T9nzmTewA03Eh/rr5VC/AKX8txRzJW/A1xY2iitLEiGW7
V8Z4wZTdKX1FY3CHBR8m9P7oglXovteLN+49uqs5kDz5rvS27oTSLUCcsUNS/JGR
rh2Yn50FJsQuBvAAdNfVNv9DFmIv+D1GM138FtJlWF+7pI0KUqfG2LZ2DD3+e/Kv
whv3OIk7Y9B/SN1e4SGNvq9IICd0UETy2AwxRzL0+deFv5S4H3EBKHoytvi0r/Ht
70E/zwEJYuET6diqgN60DNxontPgg0HT+CJ9jqRaQJVPdq+183Igtyo/W1OPDkqu
lVV+qW65DZKr+3/TTyq36HUaWOOqNo4Ra5gKo1ya9sn5aaUgfzrW87emU165qDS9
vrLA/Cd1UOxk4Dep7y7zeDkxe2C+RKkPOg7jJff+I3ZIk0SMQ8i4ILcy/EHmWSbJ
nPOUTOHUL85z8RdFOcttEK79o4D/XuquYoHMxvSm+A==
-----END CERTIFICATE-----
23 changes: 23 additions & 0 deletions test/nerves_hub_web/controllers/device_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,27 @@ defmodule NervesHubWeb.DeviceControllerTest do
|> assert_has("p", text: "Chat")
end
end

describe "certificates" do
test "download certificate for device", %{
conn: conn,
org: org,
product: product,
device: device
} do
[cert | _] = NervesHub.Devices.get_device_certificates(device)

conn =
conn
|> get(
"/org/#{org.name}/#{product.name}/devices/#{device.identifier}/certificate/#{cert.serial}/download"
)

[str] =
Plug.Conn.get_resp_header(conn, "content-disposition")

assert str =~ "attachment; filename"
assert conn.resp_body =~ "-----BEGIN CERTIFICATE-----"
end
end
end
44 changes: 44 additions & 0 deletions test/nerves_hub_web/live/devices/settings_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,48 @@ defmodule NervesHubWeb.Live.Devices.SettingsTest do
assert device.connecting_code == "dbg(\"boo\")"
end
end

describe "device certificates" do
test "can upload certificate", %{conn: conn, org: org, product: product, device: device} do
conn
|> visit("/org/#{org.name}/#{product.name}/devices/#{device.identifier}/settings")
# Device has 1 certificate as default
|> assert_has(".item", count: 1)
|> click_button("#toggle-certificate-upload", "")
|> unwrap(fn view ->
file_input(view, ".import-pem", :certificate, [
%{
name: "device-test-cert.pem",
content: File.read!("test/fixtures/ssl/device-test-cert.pem")
}
])
|> render_upload("device-test-cert.pem")

render(view)
end)
|> assert_has("div", text: "Certificate Upload Successful")
|> assert_path("/org/#{org.name}/#{product.name}/devices/#{device.identifier}/settings")
|> assert_has(".item", count: 2)
end

test "can delete certificate", %{conn: conn, org: org, product: product, device: device} do
conn
|> visit("/org/#{org.name}/#{product.name}/devices/#{device.identifier}/settings")
# Device has 1 certificate as default
|> assert_has(".item", count: 1)
|> click_link("Delete")
|> refute_has(".item")
end

test "can download certificate", %{conn: conn, org: org, product: product, device: device} do
result =
conn
|> visit("/org/#{org.name}/#{product.name}/devices/#{device.identifier}/settings")
# Device has 1 certificate as default
|> assert_has(".item", count: 1)
|> click_link("Download")

assert result.conn.resp_body =~ "-----BEGIN CERTIFICATE-----"
end
end
end

0 comments on commit a17a80a

Please sign in to comment.