diff --git a/lib/trento_web/controllers/fallback_controller.ex b/lib/trento_web/controllers/fallback_controller.ex index abf1f23565..25cfd95abc 100644 --- a/lib/trento_web/controllers/fallback_controller.ex +++ b/lib/trento_web/controllers/fallback_controller.ex @@ -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) diff --git a/lib/trento_web/controllers/v1/suse_manager_controller.ex b/lib/trento_web/controllers/v1/suse_manager_controller.ex index 9d5a4ee49a..69cc4f3da2 100644 --- a/lib/trento_web/controllers/v1/suse_manager_controller.ex +++ b/lib/trento_web/controllers/v1/suse_manager_controller.ex @@ -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 diff --git a/lib/trento_web/openapi/v1/schema/available_software_updates.ex b/lib/trento_web/openapi/v1/schema/available_software_updates.ex index fde913892a..e5e2f3ce76 100644 --- a/lib/trento_web/openapi/v1/schema/available_software_updates.ex +++ b/lib/trento_web/openapi/v1/schema/available_software_updates.ex @@ -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(%{ @@ -191,6 +206,7 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do additionalProperties: false, properties: %{ errata_details: ErrataDetails, + cves: CVEs, fixes: AdvisoryFixes } }) diff --git a/lib/trento_web/views/v1/suse_manager_view.ex b/lib/trento_web/views/v1/suse_manager_view.ex index 544d306fc1..8c26911e9b 100644 --- a/lib/trento_web/views/v1/suse_manager_view.ex +++ b/lib/trento_web/views/v1/suse_manager_view.ex @@ -65,6 +65,7 @@ defmodule TrentoWeb.V1.SUSEManagerView do def render("errata_details.json", %{ errata_details: errata_details = %{errataFrom: errataFrom}, + cves: cves, fixes: fixes }), do: %{ @@ -72,6 +73,7 @@ defmodule TrentoWeb.V1.SUSEManagerView do errata_details |> Map.drop([:errataFrom]) |> Map.put(:errata_from, errataFrom), + cves: cves, fixes: fixes } end diff --git a/test/support/factory.ex b/test/support/factory.ex index 827e19de65..9528e42f5d 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -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 _ -> diff --git a/test/trento/infrastructure/software_updates/suma_test.exs b/test/trento/infrastructure/software_updates/suma_test.exs index bf0eaf3924..4b560e6038 100644 --- a/test/trento/infrastructure/software_updates/suma_test.exs +++ b/test/trento/infrastructure/software_updates/suma_test.exs @@ -263,10 +263,7 @@ 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) @@ -274,8 +271,7 @@ defmodule Trento.Infrastructure.SoftwareUpdates.SumaTest do {: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 diff --git a/test/trento_web/controllers/v1/suse_manager_controller_test.exs b/test/trento_web/controllers/v1/suse_manager_controller_test.exs index 855082be5a..d5b24a4804 100644 --- a/test/trento_web/controllers/v1/suse_manager_controller_test.exs +++ b/test/trento_web/controllers/v1/suse_manager_controller_test.exs @@ -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 _ -> @@ -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) @@ -209,7 +217,8 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do solution: ^solution, reboot_suggested: ^reboot_suggested, restart_suggested: ^restart_suggested - } + }, + cves: ^cves } = result end @@ -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) @@ -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) diff --git a/test/trento_web/views/v1/suse_manager_view_test.exs b/test/trento_web/views/v1/suse_manager_view_test.exs index dafd41fb12..03c70fe697 100644 --- a/test/trento_web/views/v1/suse_manager_view_test.exs +++ b/test/trento_web/views/v1/suse_manager_view_test.exs @@ -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