Skip to content

Commit 37d5310

Browse files
committed
feat(pairing): add ownership voucher list endpoint
Signed-off-by: Francesco Noacco <francesco.noacco@secomind.com>
1 parent cb77c3b commit 37d5310

File tree

7 files changed

+170
-13
lines changed

7 files changed

+170
-13
lines changed

apps/astarte_pairing/lib/astarte_pairing_web/controllers/ownership_voucher_controller.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ defmodule Astarte.PairingWeb.OwnershipVoucherController do
2222
alias Astarte.FDO.OwnershipVoucher
2323
alias Astarte.FDO.OwnershipVoucher.LoadRequest
2424
alias Astarte.FDO.TO0
25+
alias Astarte.PairingWeb.OwnershipVoucherView
2526
alias Astarte.Secrets.Core, as: SecretsCore
2627

2728
action_fallback Astarte.PairingWeb.FallbackController
@@ -60,6 +61,17 @@ defmodule Astarte.PairingWeb.OwnershipVoucherController do
6061
end
6162
end
6263

64+
@doc """
65+
List ownership vouchers.
66+
"""
67+
def list_ownership_vouchers(conn, %{"realm_name" => realm_name}) do
68+
with {:ok, vouchers} <- OwnershipVoucher.list(realm_name) do
69+
conn
70+
|> put_view(OwnershipVoucherView)
71+
|> render("list_vouchers.json", ownership_vouchers: vouchers)
72+
end
73+
end
74+
6375
@doc """
6476
Returns the list of registered owner keys that are compatible with the
6577
given ownership voucher.

apps/astarte_pairing/lib/astarte_pairing_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ defmodule Astarte.PairingWeb.Router do
107107
get "/owner_keys/:key_algorithm", OwnerKeyController, :get_keys_for_algorithm
108108
get "/owner_keys/:key_algorithm/:key_name", OwnerKeyController, :get_key
109109
post "/owner_keys_for_voucher", OwnershipVoucherController, :owner_keys_for_voucher
110+
get "/ownership_vouchers", OwnershipVoucherController, :list_ownership_vouchers
110111
post "/ownership_vouchers", OwnershipVoucherController, :register
111112
end
112113

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#
2+
# This file is part of Astarte.
3+
#
4+
# Copyright 2026 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
defmodule Astarte.PairingWeb.OwnershipVoucherView do
20+
use Astarte.PairingWeb, :view
21+
22+
alias Astarte.PairingWeb.OwnershipVoucherView
23+
alias Astarte.DataAccess.FDO.OwnershipVoucher
24+
25+
def render("list_vouchers.json", %{ownership_vouchers: vouchers}) do
26+
%{
27+
data: render_many(vouchers, OwnershipVoucherView, "ownership_voucher.json")
28+
}
29+
end
30+
31+
def render("ownership_voucher.json", %{ownership_voucher: voucher}) do
32+
%OwnershipVoucher{
33+
guid: guid,
34+
replacement_guid: output_guid,
35+
voucher_data: input_voucher,
36+
output_voucher: output_voucher,
37+
status: status
38+
} = voucher
39+
40+
guid = render_one(guid, OwnershipVoucherView, "guid.json", as: :guid)
41+
output_guid = render_one(output_guid, OwnershipVoucherView, "guid.json", as: :guid)
42+
input_voucher = render_one(input_voucher, OwnershipVoucherView, "binary_voucher.json")
43+
output_voucher = render_one(output_voucher, OwnershipVoucherView, "binary_voucher.json")
44+
45+
%{
46+
guid: guid,
47+
status: status,
48+
output_guid: output_guid,
49+
input_voucher: input_voucher,
50+
output_voucher: output_voucher
51+
}
52+
end
53+
54+
def render("guid.json", %{guid: guid}) do
55+
UUID.binary_to_string!(guid)
56+
end
57+
58+
def render("binary_voucher.json", %{ownership_voucher: binary_voucher}) do
59+
# PEMs have 64-character long lines
60+
encoded =
61+
Base.encode64(binary_voucher)
62+
|> String.to_charlist()
63+
|> Enum.chunk_every(64)
64+
|> Enum.intersperse("\n")
65+
|> List.flatten()
66+
67+
"""
68+
-----BEGIN OWNERSHIP VOUCHER-----
69+
#{encoded}
70+
-----END OWNERSHIP VOUCHER-----
71+
"""
72+
end
73+
end

apps/astarte_pairing/test/astarte_pairing_web/controllers/ownership_voucher_controller_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,56 @@ defmodule Astarte.PairingWeb.Controllers.OwnershipVoucherControllerTest do
232232
end
233233
end
234234

235+
describe "list_ownership_vouchers/2" do
236+
setup :store_sample_voucher
237+
setup :add_list_path
238+
239+
test "returns the list of vouchers for the realm", context do
240+
%{auth_conn: conn, path: path} = context
241+
242+
body =
243+
conn
244+
|> get(path)
245+
|> json_response(200)
246+
247+
assert [ownership_voucher_result] = body["data"]
248+
assert UUID.string_to_binary!(ownership_voucher_result["guid"])
249+
assert ownership_voucher_result["status"] == "created"
250+
assert ownership_voucher_result["input_voucher"] == @sample_ownership_voucher_pem
251+
end
252+
end
253+
235254
defp owner_keys_for_voucher_setup(context) do
236255
%{auth_conn: conn, realm_name: realm_name} = context
237256
path = ownership_voucher_path(conn, :owner_keys_for_voucher, realm_name)
238257
%{path: path}
239258
end
259+
260+
defp store_sample_voucher(context) do
261+
%{auth_conn: conn, realm_name: realm_name} = context
262+
%{register_path: path} = register_setup(context)
263+
{:ok, owner_cose_key} = COSE.Keys.from_pem(@sample_private_key_pem)
264+
{:ok, namespace} = Secrets.create_namespace(realm_name, :es256)
265+
:ok = Secrets.import_key(@sample_key_name, :es256, owner_cose_key, namespace: namespace)
266+
267+
params = %{
268+
data: %{
269+
"ownership_voucher" => @sample_ownership_voucher_pem,
270+
"key_name" => @sample_key_name,
271+
"key_algorithm" => "es256"
272+
}
273+
}
274+
275+
conn
276+
|> post(path, params)
277+
|> json_response(200)
278+
279+
:ok
280+
end
281+
282+
defp add_list_path(context) do
283+
%{auth_conn: conn, realm_name: realm_name} = context
284+
path = ownership_voucher_path(conn, :list_ownership_vouchers, realm_name)
285+
%{path: path}
286+
end
240287
end

libs/astarte_data_access/lib/astarte_data_access/fdo/queries.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ defmodule Astarte.DataAccess.FDO.Queries do
5858
Repo.fetch(query, guid, consistency: consistency)
5959
end
6060

61+
def list_ownership_vouchers(realm_name) do
62+
keyspace = Realm.keyspace_name(realm_name)
63+
consistency = Consistency.domain_model(:read)
64+
opts = [consistency: consistency, prefix: keyspace]
65+
66+
Repo.fetch_all(OwnershipVoucher, opts)
67+
end
68+
6169
def get_owner_key_params(realm_name, guid) do
6270
keyspace_name = Realm.keyspace_name(realm_name)
6371

libs/astarte_data_access/test/fdo/queries_test.exs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,21 +201,18 @@ defmodule Astarte.DataAccess.FDO.QueriesTest do
201201
end
202202
end
203203

204-
describe "mark_device_as_claimed/2" do
205-
setup do
206-
guid = :crypto.strong_rand_bytes(16)
207-
208-
on_exit(fn -> Queries.delete_ownership_voucher(@realm, guid) end)
209-
210-
Queries.create_ownership_voucher(@realm, %{
211-
guid: guid,
212-
key_name: "key",
213-
key_algorithm: :es256,
214-
voucher_data: <<0>>
215-
})
204+
describe "list_ownership_vouchers/1" do
205+
setup :setup_voucher
216206

217-
%{guid: guid}
207+
test "returns the list of vouchers", %{guid: guid} do
208+
assert {:ok, vouchers} = Queries.list_ownership_vouchers(@realm)
209+
assert Enum.all?(vouchers, &is_struct(&1, OwnershipVoucher))
210+
assert Enum.find(vouchers, &(&1.guid == guid))
218211
end
212+
end
213+
214+
describe "mark_device_as_claimed/2" do
215+
setup :setup_voucher
219216

220217
test "updates the status of the ownership voucher", %{guid: guid} do
221218
opts = [prefix: Realm.keyspace_name(@realm)]
@@ -322,4 +319,19 @@ defmodule Astarte.DataAccess.FDO.QueriesTest do
322319
key_algorithm: key_algorithm
323320
}
324321
end
322+
323+
defp setup_voucher(_context) do
324+
guid = :crypto.strong_rand_bytes(16)
325+
326+
on_exit(fn -> Queries.delete_ownership_voucher(@realm, guid) end)
327+
328+
Queries.create_ownership_voucher(@realm, %{
329+
guid: guid,
330+
key_name: "key",
331+
key_algorithm: :es256,
332+
voucher_data: <<0>>
333+
})
334+
335+
%{guid: guid}
336+
end
325337
end

libs/astarte_fdo/lib/ownership_voucher/ownership_voucher.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ defmodule Astarte.FDO.OwnershipVoucher do
3232
end
3333
end
3434

35+
def list(realm_name) do
36+
Queries.list_ownership_vouchers(realm_name)
37+
end
38+
3539
def fetch(realm_name, guid) do
3640
case Queries.get_ownership_voucher(realm_name, guid) do
3741
{:ok, ownership_voucher_cbor} ->

0 commit comments

Comments
 (0)