Skip to content

Commit

Permalink
Return CVEs from Errata Details endpoint (#2733)
Browse files Browse the repository at this point in the history
* Return CVEs from `v1/software_updates/errata_details/:advisory_name`

* Create CVE factory
  • Loading branch information
jamie-suse authored Jul 4, 2024
1 parent b9414b7 commit c75617d
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 9 deletions.
7 changes: 7 additions & 0 deletions lib/trento_web/controllers/fallback_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ defmodule TrentoWeb.FallbackController do
|> render(:"422", reason: "Unable to retrieve errata details for this advisory.")
end

def call(conn, {:error, :error_getting_cves}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(ErrorView)
|> render(:"422", reason: "Unable to retrieve CVEs for this advisory.")
end

def call(conn, {:error, :error_getting_fixes}) do
conn
|> put_status(:unprocessable_entity)
Expand Down
3 changes: 2 additions & 1 deletion lib/trento_web/controllers/v1/suse_manager_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ defmodule TrentoWeb.V1.SUSEManagerController do
@spec errata_details(Plug.Conn.t(), any) :: Plug.Conn.t()
def errata_details(conn, %{advisory_name: advisory_name}) do
with {:ok, errata_details} <- Discovery.get_errata_details(advisory_name),
{:ok, cves} <- Discovery.get_cves(advisory_name),
{:ok, fixes} <- Discovery.get_bugzilla_fixes(advisory_name) do
render(conn, %{errata_details: errata_details, fixes: fixes})
render(conn, %{errata_details: errata_details, cves: cves, fixes: fixes})
end
end
end
16 changes: 16 additions & 0 deletions lib/trento_web/openapi/v1/schema/available_software_updates.ex
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do
})
end

defmodule CVEs do
@moduledoc false
OpenApiSpex.schema(%{
title: "CVEs",
description: "List of CVEs applicable to the errata with the given advisory name.",
type: :array,
additionalProperties: false,
items: %Schema{
title: "CVE",
description: "A fix for a publicly known security vulnerability",
type: :string
}
})
end

defmodule AdvisoryFixes do
@moduledoc false
OpenApiSpex.schema(%{
Expand All @@ -191,6 +206,7 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do
additionalProperties: false,
properties: %{
errata_details: ErrataDetails,
cves: CVEs,
fixes: AdvisoryFixes
}
})
Expand Down
2 changes: 2 additions & 0 deletions lib/trento_web/views/v1/suse_manager_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ defmodule TrentoWeb.V1.SUSEManagerView do

def render("errata_details.json", %{
errata_details: errata_details = %{errataFrom: errataFrom},
cves: cves,
fixes: fixes
}),
do: %{
errata_details:
errata_details
|> Map.drop([:errataFrom])
|> Map.put(:errata_from, errataFrom),
cves: cves,
fixes: fixes
}
end
7 changes: 7 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,13 @@ defmodule Trento.Factory do
}
end

def cve_factory(attrs) do
year = Enum.random(1_991..2_024)
id = Enum.random(0..9_999)
%{year: year, id: id} = Map.merge(%{year: year, id: id}, attrs)
"CVE-#{year}-#{id}"
end

def bugzilla_fix_factory do
1..Enum.random(1..4)
|> Enum.map(fn _ ->
Expand Down
8 changes: 2 additions & 6 deletions test/trento/infrastructure/software_updates/suma_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -263,19 +263,15 @@ defmodule Trento.Infrastructure.SoftwareUpdates.SumaTest do
advisory_name = Faker.UUID.v4()

%{result: cves} =
suma_response_body = %{
success: true,
result: Enum.map(1..10, fn _ -> Faker.UUID.v4() end)
}
suma_response_body = %{success: true, result: build_list(10, :cve)}

expect(SumaAuthMock, :authenticate, 1, fn -> {:ok, authenticated_state()} end)

expect(SumaApiMock, :get_cves, 1, fn _, _, ^advisory_name, _ ->
{:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(suma_response_body)}}
end)

assert {:ok, ^cves} =
Suma.get_cves(advisory_name)
assert {:ok, ^cves} = Suma.get_cves(advisory_name)
end

test "should return a proper error when getting CVEs for a patch fails" do
Expand Down
45 changes: 44 additions & 1 deletion test/trento_web/controllers/v1/suse_manager_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do
{:ok, errata_details}
end)

cves = build_list(10, :cve)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ ->
{:ok, cves}
end)

fixes = build(:bugzilla_fix)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ ->
Expand All @@ -185,6 +191,8 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do

%{"fixes" => json_fixes} = json

# The returned struct from `assert_schema/3` empties the dynamic Map in `fixes`.
# Assert on the JSON response that the `fixes` Map contains entries.
assert fixes |> Map.keys() |> length == json_fixes |> Map.keys() |> length

result = assert_schema(json, "ErrataDetailsResponse", api_spec)
Expand All @@ -209,7 +217,8 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do
solution: ^solution,
reboot_suggested: ^reboot_suggested,
restart_suggested: ^restart_suggested
}
},
cves: ^cves
} = result
end

Expand All @@ -223,6 +232,36 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do
{:error, :error_getting_errata_details}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ ->
{:ok, build_list(10, :cve)}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ ->
{:ok, build(:bugzilla_fix)}
end)

advisory_name = Faker.Pokemon.name()

conn
|> get("/api/v1/software_updates/errata_details/#{advisory_name}")
|> json_response(:unprocessable_entity)
|> assert_schema("UnprocessableEntity", api_spec)
end

test "should return 422 when advisory CVEs are not found", %{
conn: conn,
api_spec: api_spec
} do
insert_software_updates_settings()

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_errata_details, 1, fn _ ->
{:ok, build(:errata_details)}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ ->
{:error, :error_getting_cves}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ ->
{:ok, build(:bugzilla_fix)}
end)
Expand All @@ -245,6 +284,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do
{:ok, build(:errata_details)}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ ->
{:ok, build_list(10, :cve)}
end)

expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ ->
{:error, :error_getting_fixes}
end)
Expand Down
7 changes: 6 additions & 1 deletion test/trento_web/views/v1/suse_manager_view_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,25 @@ defmodule TrentoWeb.V1.SUSEManagerViewTest do
describe "renders errata_details.json" do
test "should render relevant fields" do
%{errataFrom: errata_from} = errata_details = build(:errata_details)
fixes = build(:bugzilla_fix)

errata_details_sans_errata_from = Map.delete(errata_details, :errataFrom)

expected_errata_details =
Map.put(errata_details_sans_errata_from, :errata_from, errata_from)

cves = build_list(10, :cve)

fixes = build(:bugzilla_fix)

assert %{
errata_details: ^expected_errata_details,
cves: ^cves,
fixes: ^fixes
} =
render(SUSEManagerView, "errata_details.json", %{
errata_details:
Map.put(errata_details_sans_errata_from, :errataFrom, errata_from),
cves: cves,
fixes: fixes
})
end
Expand Down

0 comments on commit c75617d

Please sign in to comment.