Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 22 additions & 20 deletions src/rebar3_sbom_cpe.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(rebar3_sbom_cpe).

-export([hex/3]).
-export([cpe/3]).

% Includes
-include("rebar3_sbom.hrl").
Expand All @@ -25,60 +25,62 @@

%--- API -----------------------------------------------------------------------

-spec hex(Name, Version, Url) -> CPE when
-spec cpe(Name, Version, Url) -> CPE when
Name :: bitstring(),
Version :: bitstring(),
Url :: bitstring() | undefined,
CPE :: bitstring().
hex(Name, undefined, Url) ->
hex(Name, <<"*">>, Url);
hex(<<"hex_core">>, Version, _) ->
cpe(Name, undefined, Url) ->
cpe(Name, <<"*">>, Url);
cpe(<<"hex_core">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":hex:hex_core:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"plug">>, Version, _) ->
cpe(<<"plug">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":elixir-plug:plug:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"phoenix">>, Version, _) ->
cpe(<<"phoenix">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":phoenixframework:phoenix:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"coherence">>, Version, _) ->
cpe(<<"coherence">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":coherence_project:coherence:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"xain">>, Version, _) ->
cpe(<<"xain">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":emetrotel:xain:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"sweet_xml">>, Version, _) ->
cpe(<<"sweet_xml">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":kbrw:sweet_xml:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"erlang/otp">>, Version, _) ->
cpe(<<"erlang/otp">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":erlang:erlang\/otp:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"rebar3">>, Version, _) ->
cpe(<<"rebar3">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":erlang:rebar3:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(<<"elixir">>, Version, _) ->
cpe(<<"elixir">>, Version, _) ->
<<?CPE_PREFIX/binary, ":", ?CPE_PART_APPLICATION/binary,
":elixir-lang:elixir:", Version/bitstring, ?CPE_POSTFIX/binary>>;
hex(_Name, _Version, undefined) ->
cpe(_Name, _Version, undefined) ->
undefined;
hex(Name, Version, Url) ->
cpe(Name, Version, Url) ->
Organization = github_url(Url),
cpe(Organization, Name, Version).
build_cpe(Organization, Name, Version).

%--- Private -------------------------------------------------------------------

-spec github_url(Url) -> Organization when
Url :: bitstring(),
Organization :: bitstring().
github_url(Url) ->
<<"https://github.com/", Rest/bitstring>> = Url,
github_url(<<"https://github.com/", Rest/bitstring>>) ->
[Organization | _] = string:split(Rest, "/"),
Organization;
github_url(<<"git@github.com:", Rest/bitstring>>) ->
[Organization | _] = string:split(Rest, "/"),
Organization.

-spec cpe(Organization, Name, Version) -> CPE when
-spec build_cpe(Organization, Name, Version) -> CPE when
Organization :: bitstring(),
Name :: bitstring(),
Version :: bitstring(),
CPE :: bitstring().
cpe(Organization, Name, Version) ->
build_cpe(Organization, Name, Version) ->
<<?CPE_PREFIX/binary, ":a:", Organization/binary, ":", Name/binary, ":", Version/bitstring, ?CPE_POSTFIX/binary>>.
38 changes: 28 additions & 10 deletions src/rebar3_sbom_prv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ dep_info(Dep) ->
Licenses = lists:usort(Licenses0 ++ HexMetadataLicenses),
Links = proplists:get_value(links, Details, []),
GitHubLink = get_github_link(HexMetadata, Links),
CPE = rebar3_sbom_cpe:hex(Name, list_to_binary(Version), GitHubLink),
Common =
[
{authors, proplists:get_value(maintainers, Details, [])},
Expand All @@ -104,7 +103,7 @@ dep_info(Dep) ->
{external_references, ExternalReferences},
{dependencies, Deps},
{scope, required},
{cpe, CPE}
{github_link, GitHubLink}
],
dep_info(Name, Version, Source, Common).

Expand Down Expand Up @@ -177,45 +176,64 @@ valid_external_reference_types() ->
"patent", "patent-family", "patent-assertion", "citation", "other"].

dep_info(_Name, _Version, {pkg, Name, Version, Sha256}, Common) ->
GitHubLink = proplists:get_value(github_link, Common, undefined),
[
{name, Name},
{version, Version},
{purl, rebar3_sbom_purl:hex(Name, Version)},
{sha256, string:lowercase(Sha256)}
{sha256, string:lowercase(Sha256)},
{cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)}
| Common ];

dep_info(_Name, _Version, {pkg, Name, Version, _InnerChecksum, OuterChecksum, _RepoConfig}, Common) ->
GitHubLink = proplists:get_value(github_link, Common, undefined),
[
{name, Name},
{version, Version},
{purl, rebar3_sbom_purl:hex(Name, Version)},
{sha256, string:lowercase(OuterChecksum)}
{sha256, string:lowercase(OuterChecksum)},
{cpe, rebar3_sbom_cpe:cpe(Name, Version, GitHubLink)}
| Common ];

dep_info(Name, DepVersion, {git, Git, GitRef}, Common) ->
{Version, Purl} =
{Version, Purl, CPE} =
case GitRef of
{tag, Tag} ->
{Tag, rebar3_sbom_purl:git(Name, Git, Tag)};
GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Tag), list_to_binary(Git)),
{Tag, rebar3_sbom_purl:git(Name, Git, Tag), GeneratedCPE};
{branch, Branch} ->
{DepVersion, rebar3_sbom_purl:git(Name, Git, Branch)};
GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Branch), list_to_binary(Git)),
{DepVersion, rebar3_sbom_purl:git(Name, Git, Branch), GeneratedCPE};
{ref, Ref} ->
{DepVersion, rebar3_sbom_purl:git(Name, Git, Ref)}
GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Ref), list_to_binary(Git)),
{DepVersion, rebar3_sbom_purl:git(Name, Git, Ref), GeneratedCPE}
end,
[
{name, Name},
{version, Version},
{purl, Purl}
{purl, Purl},
{cpe, CPE}
| maybe_update_licenses(Purl, Common) ];
dep_info(Name, Version, {git_subdir, Git, Ref, _Dir}, Common) ->
dep_info(Name, Version, {git, Git, Ref}, Common);

dep_info(Name, Version, checkout, Common) ->
GitHubLink = proplists:get_value(github_link, Common, undefined),
[
{name, Name},
{version, Version},
{purl, rebar3_sbom_purl:local_otp_app(Name, Version)},
{cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)}
| Common ];

dep_info(Name, Version, root_app, Common) ->
GitHubLink = proplists:get_value(github_link, Common, undefined),
Purl = rebar3_sbom_purl:hex(Name, Version),
[
{name, Name},
{version, Version},
{purl, Purl}
{purl, Purl},
{cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)}
| Common ].

filepath(?DEFAULT_OUTPUT, Format) ->
Expand Down
8 changes: 7 additions & 1 deletion src/rebar3_sbom_purl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

% https://github.com/package-url/purl-spec

-export([hex/2, git/3, github/2, bitbucket/2]).
-export([hex/2, git/3, github/2, bitbucket/2, local_otp_app/2, local/2]).

hex(Name, Version) ->
purl(["hex", string:lowercase(Name)], Version).
Expand Down Expand Up @@ -43,6 +43,12 @@ bitbucket(Repo, Ref) ->
[Organization, Name | _] = string:split(Repo, "/"),
purl(["bitbucket", string:lowercase(Organization), string:lowercase(Name)], Ref).

local_otp_app(Name, Version) ->
purl(["otp", string:lowercase(Name)], Version).

local(Name, Version) ->
purl(["generic", string:lowercase(Name)], Version).

purl(PathSegments, Version) ->
Path = lists:join("/", [escape(Segment) || Segment <- PathSegments]),
unicode:characters_to_binary(io_lib:format("pkg:~s@~s", [Path, escape(Version)])).
Expand Down
7 changes: 7 additions & 0 deletions test/local_app/_checkouts/checkout_app/rebar.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{erl_opts, [debug_info]}.
{deps, []}.

{shell, [
%% {config, "config/sys.config"},
{apps, [checkout_app]}
]}.
14 changes: 14 additions & 0 deletions test/local_app/_checkouts/checkout_app/src/checkout_app.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{application, checkout_app, [
{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {checkout_app_app, []}},
{applications, [
kernel,
stdlib
]},
{env, []},
{modules, []},
{licenses, ["Apache-2.0"]},
{links, []}
]}.
18 changes: 18 additions & 0 deletions test/local_app/_checkouts/checkout_app/src/checkout_app_app.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
%%%-------------------------------------------------------------------
%% @doc checkout_app public API
%% @end
%%%-------------------------------------------------------------------

-module(checkout_app_app).

-behaviour(application).

-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
checkout_app_sup:start_link().

stop(_State) ->
ok.

%% internal functions
37 changes: 37 additions & 0 deletions test/local_app/_checkouts/checkout_app/src/checkout_app_sup.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
%%%-------------------------------------------------------------------
%% @doc checkout_app top level supervisor.
%% @end
%%%-------------------------------------------------------------------

-module(checkout_app_sup).

-behaviour(supervisor).

-export([start_link/0]).

-export([init/1]).

-define(SERVER, ?MODULE).

start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).

%% sup_flags() = #{strategy => strategy(), % optional
%% intensity => non_neg_integer(), % optional
%% period => pos_integer()} % optional
%% child_spec() = #{id => child_id(), % mandatory
%% start => mfargs(), % mandatory
%% restart => restart(), % optional
%% shutdown => shutdown(), % optional
%% type => worker(), % optional
%% modules => modules()} % optional
init([]) ->
SupFlags = #{
strategy => one_for_all,
intensity => 0,
period => 1
},
ChildSpecs = [],
{ok, {SupFlags, ChildSpecs}}.

%% internal functions
4 changes: 3 additions & 1 deletion test/local_app/rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
{rebar3_sbom, [
]}.

{deps, []}.
{deps, [
checkout_app
]}.

{shell, [
%% {config, "config/sys.config"},
Expand Down
42 changes: 21 additions & 21 deletions test/rebar3_sbom_cpe_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,65 +51,65 @@ end_per_testcase(_, _Config) ->
%--- Test cases ----------------------------------------------------------------

hex_core_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"hex_core">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"hex_core">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:hex:hex_core:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"hex_core">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"hex_core">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:hex:hex_core:*:*:*:*:*:*:*:*">>, CPENoVersion).

plug_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"plug">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"plug">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:elixir-plug:plug:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"plug">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"plug">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:elixir-plug:plug:*:*:*:*:*:*:*:*">>, CPENoVersion).

phoenix_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"phoenix">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"phoenix">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:phoenixframework:phoenix:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"phoenix">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"phoenix">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:phoenixframework:phoenix:*:*:*:*:*:*:*:*">>, CPENoVersion).

coherence_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"coherence">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"coherence">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:coherence_project:coherence:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"coherence">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"coherence">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:coherence_project:coherence:*:*:*:*:*:*:*:*">>, CPENoVersion).

xain_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"xain">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"xain">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:emetrotel:xain:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"xain">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"xain">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:emetrotel:xain:*:*:*:*:*:*:*:*">>, CPENoVersion).

sweet_xml_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"sweet_xml">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"sweet_xml">>, <<"1.0.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:kbrw:sweet_xml:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"sweet_xml">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"sweet_xml">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:kbrw:sweet_xml:*:*:*:*:*:*:*:*">>, CPENoVersion).

erlang_otp_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"erlang/otp">>, <<"28.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"erlang/otp">>, <<"28.0">>, undefined),
?assertEqual(<<"cpe:2.3:a:erlang:erlang\/otp:28.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"erlang/otp">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"erlang/otp">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*">>, CPENoVersion).

rebar3_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"rebar3">>, <<"3.14.1">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"rebar3">>, <<"3.14.1">>, undefined),
?assertEqual(<<"cpe:2.3:a:erlang:rebar3:3.14.1:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"rebar3">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"rebar3">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*">>, CPENoVersion).

elixir_cpe_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"elixir">>, <<"1.19.3">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"elixir">>, <<"1.19.3">>, undefined),
?assertEqual(<<"cpe:2.3:a:elixir-lang:elixir:1.19.3:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"elixir">>, undefined, undefined),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"elixir">>, undefined, undefined),
?assertEqual(<<"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*">>, CPENoVersion).

default_behavior_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"my_package">>, <<"1.0.0">>, <<"https://github.com/my_org/my_package">>),
CPE = rebar3_sbom_cpe:cpe(<<"my_package">>, <<"1.0.0">>, <<"https://github.com/my_org/my_package">>),
?assertEqual(<<"cpe:2.3:a:my_org:my_package:1.0.0:*:*:*:*:*:*:*">>, CPE),
CPENoVersion = rebar3_sbom_cpe:hex(<<"my_package">>, undefined, <<"https://github.com/my_org/my_package">>),
CPENoVersion = rebar3_sbom_cpe:cpe(<<"my_package">>, undefined, <<"https://github.com/my_org/my_package">>),
?assertEqual(<<"cpe:2.3:a:my_org:my_package:*:*:*:*:*:*:*:*">>, CPENoVersion).

no_url_test(_) ->
CPE = rebar3_sbom_cpe:hex(<<"non_hex_package">>, <<"1.0.0">>, undefined),
CPE = rebar3_sbom_cpe:cpe(<<"non_hex_package">>, <<"1.0.0">>, undefined),
?assertEqual(undefined, CPE).
Loading