diff --git a/proto/hex_pb_policy.proto b/proto/hex_pb_policy.proto index 47192e1e..c1220a87 100644 --- a/proto/hex_pb_policy.proto +++ b/proto/hex_pb_policy.proto @@ -41,10 +41,9 @@ message RepositoryPolicy { // override (those bypass all limits). optional Restriction restriction = 2; - // Per-package final say, evaluated against each release in this repository. - // An ALLOW override permits the release immediately and bypasses - // `restriction`; a DENY override blocks it. When multiple overrides match a - // release, the one with the most specific requirement wins. + // Per-package overrides. ALLOW and DENY provide a final decision for the + // matching release. ADVISORY, RETIREMENT, and COOLDOWN each bypass only the + // selected restriction. Invalid or unknown entries are ignored. repeated Override overrides = 3; } @@ -58,8 +57,8 @@ message Restriction { repeated RetirementReason retirement_reasons = 2 [packed=true]; // Minimum release age. Same duration grammar as the Hex cooldown config - // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. If multiple - // active policies declare cooldowns, the effective cooldown is the strictest. + // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. The active + // policy and local cooldown compose by strictest-wins. optional string cooldown = 3; } @@ -72,16 +71,37 @@ message PackageRef { } message Override { - // Whether this override permits or blocks the matching release. + // The effect of this override. The fields permitted for each action are + // described below. Unknown actions must be ignored. required OverrideAction action = 1; // The package (and optional requirement) the override applies to. required PackageRef ref = 2; + + // ADVISORY requires advisory_id and forbids retirement_reason. Advisory + // identifiers match primary advisory IDs and aliases without regard to + // case. RETIREMENT requires retirement_reason and forbids advisory_id. + // ALLOW, DENY, and COOLDOWN forbid both selector fields. + optional string advisory_id = 3; + optional RetirementReason retirement_reason = 4; + + // Optional UTF-8 explanation surfaced by clients for every override action. + // At most 500 Unicode code points; control, format, line separator, and + // paragraph separator characters are invalid. This is public when the + // policy visibility is VISIBILITY_PUBLIC. + optional string comment = 5; } enum OverrideAction { - // Permit the release and bypass `restriction`. + // Permit the release and bypass every policy restriction. OVERRIDE_ACTION_ALLOW = 0; // Block the release. OVERRIDE_ACTION_DENY = 1; + // Accept only the advisory selected by advisory_id. + OVERRIDE_ACTION_ADVISORY = 2; + // Accept only a retirement with the selected retirement_reason. + OVERRIDE_ACTION_RETIREMENT = 3; + // Bypass only the policy cooldown. Local cooldown configuration still + // applies independently. + OVERRIDE_ACTION_COOLDOWN = 4; } diff --git a/src/hex_pb_policy.erl b/src/hex_pb_policy.erl index 35c73d99..dfaa0a97 100644 --- a/src/hex_pb_policy.erl +++ b/src/hex_pb_policy.erl @@ -54,7 +54,7 @@ %% enumerated types -type 'Visibility'() :: 'VISIBILITY_PRIVATE' | 'VISIBILITY_PUBLIC'. --type 'OverrideAction'() :: 'OVERRIDE_ACTION_ALLOW' | 'OVERRIDE_ACTION_DENY'. +-type 'OverrideAction'() :: 'OVERRIDE_ACTION_ALLOW' | 'OVERRIDE_ACTION_DENY' | 'OVERRIDE_ACTION_ADVISORY' | 'OVERRIDE_ACTION_RETIREMENT' | 'OVERRIDE_ACTION_COOLDOWN'. -type 'RetirementReason'() :: 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED'. -type 'AdvisorySeverity'() :: 'SEVERITY_NONE' | 'SEVERITY_LOW' | 'SEVERITY_MEDIUM' | 'SEVERITY_HIGH' | 'SEVERITY_CRITICAL'. -export_type(['Visibility'/0, 'OverrideAction'/0, 'RetirementReason'/0, 'AdvisorySeverity'/0]). @@ -86,8 +86,11 @@ }. -type 'Override'() :: - #{action => 'OVERRIDE_ACTION_ALLOW' | 'OVERRIDE_ACTION_DENY' | integer(), % = 1, required, enum OverrideAction - ref => 'PackageRef'() % = 2, required + #{action => 'OVERRIDE_ACTION_ALLOW' | 'OVERRIDE_ACTION_DENY' | 'OVERRIDE_ACTION_ADVISORY' | 'OVERRIDE_ACTION_RETIREMENT' | 'OVERRIDE_ACTION_COOLDOWN' | integer(), % = 1, required, enum OverrideAction + ref => 'PackageRef'(), % = 2, required + advisory_id => unicode:chardata(), % = 3, optional + retirement_reason => 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED' | integer(), % = 4, optional, enum RetirementReason + comment => unicode:chardata() % = 5, optional }. -type 'Package'() :: @@ -234,9 +237,21 @@ encode_msg_PackageRef(#{package := F1} = M, Bin, TrUserData) -> encode_msg_Override(Msg, TrUserData) -> encode_msg_Override(Msg, <<>>, TrUserData). -encode_msg_Override(#{action := F1, ref := F2}, Bin, TrUserData) -> +encode_msg_Override(#{action := F1, ref := F2} = M, Bin, TrUserData) -> B1 = begin TrF1 = id(F1, TrUserData), e_enum_OverrideAction(TrF1, <>, TrUserData) end, - begin TrF2 = id(F2, TrUserData), e_mfield_Override_ref(TrF2, <>, TrUserData) end. + B2 = begin TrF2 = id(F2, TrUserData), e_mfield_Override_ref(TrF2, <>, TrUserData) end, + B3 = case M of + #{advisory_id := F3} -> begin TrF3 = id(F3, TrUserData), e_type_string(TrF3, <>, TrUserData) end; + _ -> B2 + end, + B4 = case M of + #{retirement_reason := F4} -> begin TrF4 = id(F4, TrUserData), e_enum_RetirementReason(TrF4, <>, TrUserData) end; + _ -> B3 + end, + case M of + #{comment := F5} -> begin TrF5 = id(F5, TrUserData), e_type_string(TrF5, <>, TrUserData) end; + _ -> B4 + end. encode_msg_Package(Msg, TrUserData) -> encode_msg_Package(Msg, <<>>, TrUserData). @@ -462,6 +477,9 @@ e_enum_Visibility(V, Bin, _TrUserData) -> e_varint(V, Bin). e_enum_OverrideAction('OVERRIDE_ACTION_ALLOW', Bin, _TrUserData) -> <>; e_enum_OverrideAction('OVERRIDE_ACTION_DENY', Bin, _TrUserData) -> <>; +e_enum_OverrideAction('OVERRIDE_ACTION_ADVISORY', Bin, _TrUserData) -> <>; +e_enum_OverrideAction('OVERRIDE_ACTION_RETIREMENT', Bin, _TrUserData) -> <>; +e_enum_OverrideAction('OVERRIDE_ACTION_COOLDOWN', Bin, _TrUserData) -> <>; e_enum_OverrideAction(V, Bin, _TrUserData) -> e_varint(V, Bin). e_enum_RetirementReason('RETIRED_OTHER', Bin, _TrUserData) -> <>; @@ -927,45 +945,69 @@ skip_32_PackageRef(<<_:32, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> skip_64_PackageRef(<<_:64, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> dfp_read_field_def_PackageRef(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData). -decode_msg_Override(Bin, TrUserData) -> dfp_read_field_def_Override(Bin, 0, 0, 0, id('$undef', TrUserData), id('$undef', TrUserData), TrUserData). +decode_msg_Override(Bin, TrUserData) -> dfp_read_field_def_Override(Bin, 0, 0, 0, id('$undef', TrUserData), id('$undef', TrUserData), id('$undef', TrUserData), id('$undef', TrUserData), id('$undef', TrUserData), TrUserData). -dfp_read_field_def_Override(<<8, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> d_field_Override_action(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData); -dfp_read_field_def_Override(<<18, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> d_field_Override_ref(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData); -dfp_read_field_def_Override(<<>>, 0, 0, _, F@_1, F@_2, _) -> +dfp_read_field_def_Override(<<8, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> d_field_Override_action(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Override(<<18, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> d_field_Override_ref(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Override(<<26, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> d_field_Override_advisory_id(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Override(<<32, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> d_field_Override_retirement_reason(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Override(<<42, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> d_field_Override_comment(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Override(<<>>, 0, 0, _, F@_1, F@_2, F@_3, F@_4, F@_5, _) -> S1 = #{action => F@_1}, - if F@_2 == '$undef' -> S1; - true -> S1#{ref => F@_2} + S2 = if F@_2 == '$undef' -> S1; + true -> S1#{ref => F@_2} + end, + S3 = if F@_3 == '$undef' -> S2; + true -> S2#{advisory_id => F@_3} + end, + S4 = if F@_4 == '$undef' -> S3; + true -> S3#{retirement_reason => F@_4} + end, + if F@_5 == '$undef' -> S4; + true -> S4#{comment => F@_5} end; -dfp_read_field_def_Override(Other, Z1, Z2, F, F@_1, F@_2, TrUserData) -> dg_read_field_def_Override(Other, Z1, Z2, F, F@_1, F@_2, TrUserData). +dfp_read_field_def_Override(Other, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> dg_read_field_def_Override(Other, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). -dg_read_field_def_Override(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, TrUserData) when N < 32 - 7 -> dg_read_field_def_Override(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, TrUserData); -dg_read_field_def_Override(<<0:1, X:7, Rest/binary>>, N, Acc, _, F@_1, F@_2, TrUserData) -> +dg_read_field_def_Override(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 32 - 7 -> dg_read_field_def_Override(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dg_read_field_def_Override(<<0:1, X:7, Rest/binary>>, N, Acc, _, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> Key = X bsl N + Acc, case Key of - 8 -> d_field_Override_action(Rest, 0, 0, 0, F@_1, F@_2, TrUserData); - 18 -> d_field_Override_ref(Rest, 0, 0, 0, F@_1, F@_2, TrUserData); + 8 -> d_field_Override_action(Rest, 0, 0, 0, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 18 -> d_field_Override_ref(Rest, 0, 0, 0, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 26 -> d_field_Override_advisory_id(Rest, 0, 0, 0, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 32 -> d_field_Override_retirement_reason(Rest, 0, 0, 0, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 42 -> d_field_Override_comment(Rest, 0, 0, 0, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); _ -> case Key band 7 of - 0 -> skip_varint_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, TrUserData); - 1 -> skip_64_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, TrUserData); - 2 -> skip_length_delimited_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, TrUserData); - 3 -> skip_group_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, TrUserData); - 5 -> skip_32_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, TrUserData) + 0 -> skip_varint_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 1 -> skip_64_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 2 -> skip_length_delimited_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 3 -> skip_group_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); + 5 -> skip_32_Override(Rest, 0, 0, Key bsr 3, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) end end; -dg_read_field_def_Override(<<>>, 0, 0, _, F@_1, F@_2, _) -> +dg_read_field_def_Override(<<>>, 0, 0, _, F@_1, F@_2, F@_3, F@_4, F@_5, _) -> S1 = #{action => F@_1}, - if F@_2 == '$undef' -> S1; - true -> S1#{ref => F@_2} + S2 = if F@_2 == '$undef' -> S1; + true -> S1#{ref => F@_2} + end, + S3 = if F@_3 == '$undef' -> S2; + true -> S2#{advisory_id => F@_3} + end, + S4 = if F@_4 == '$undef' -> S3; + true -> S3#{retirement_reason => F@_4} + end, + if F@_5 == '$undef' -> S4; + true -> S4#{comment => F@_5} end. -d_field_Override_action(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, TrUserData) when N < 57 -> d_field_Override_action(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, TrUserData); -d_field_Override_action(<<0:1, X:7, Rest/binary>>, N, Acc, F, _, F@_2, TrUserData) -> +d_field_Override_action(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> d_field_Override_action(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Override_action(<<0:1, X:7, Rest/binary>>, N, Acc, F, _, F@_2, F@_3, F@_4, F@_5, TrUserData) -> {NewFValue, RestF} = {id(d_enum_OverrideAction(begin <> = <<(X bsl N + Acc):32/unsigned-native>>, id(Res, TrUserData) end), TrUserData), Rest}, - dfp_read_field_def_Override(RestF, 0, 0, F, NewFValue, F@_2, TrUserData). + dfp_read_field_def_Override(RestF, 0, 0, F, NewFValue, F@_2, F@_3, F@_4, F@_5, TrUserData). -d_field_Override_ref(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, TrUserData) when N < 57 -> d_field_Override_ref(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, TrUserData); -d_field_Override_ref(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, Prev, TrUserData) -> +d_field_Override_ref(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> d_field_Override_ref(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Override_ref(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, Prev, F@_3, F@_4, F@_5, TrUserData) -> {NewFValue, RestF} = begin Len = X bsl N + Acc, <> = Rest, {id(decode_msg_PackageRef(Bs, TrUserData), TrUserData), Rest2} end, dfp_read_field_def_Override(RestF, 0, @@ -975,24 +1017,42 @@ d_field_Override_ref(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, Prev, TrUserDat if Prev == '$undef' -> NewFValue; true -> merge_msg_PackageRef(Prev, NewFValue, TrUserData) end, + F@_3, + F@_4, + F@_5, TrUserData). -skip_varint_Override(<<1:1, _:7, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> skip_varint_Override(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData); -skip_varint_Override(<<0:1, _:7, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData). +d_field_Override_advisory_id(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> d_field_Override_advisory_id(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Override_advisory_id(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, _, F@_4, F@_5, TrUserData) -> + {NewFValue, RestF} = begin Len = X bsl N + Acc, <> = Rest, Bytes2 = binary:copy(Bytes), {id(Bytes2, TrUserData), Rest2} end, + dfp_read_field_def_Override(RestF, 0, 0, F, F@_1, F@_2, NewFValue, F@_4, F@_5, TrUserData). + +d_field_Override_retirement_reason(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> d_field_Override_retirement_reason(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Override_retirement_reason(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, _, F@_5, TrUserData) -> + {NewFValue, RestF} = {id(d_enum_RetirementReason(begin <> = <<(X bsl N + Acc):32/unsigned-native>>, id(Res, TrUserData) end), TrUserData), Rest}, + dfp_read_field_def_Override(RestF, 0, 0, F, F@_1, F@_2, F@_3, NewFValue, F@_5, TrUserData). -skip_length_delimited_Override(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, TrUserData) when N < 57 -> skip_length_delimited_Override(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, TrUserData); -skip_length_delimited_Override(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, TrUserData) -> +d_field_Override_comment(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> d_field_Override_comment(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Override_comment(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, _, TrUserData) -> + {NewFValue, RestF} = begin Len = X bsl N + Acc, <> = Rest, Bytes2 = binary:copy(Bytes), {id(Bytes2, TrUserData), Rest2} end, + dfp_read_field_def_Override(RestF, 0, 0, F, F@_1, F@_2, F@_3, F@_4, NewFValue, TrUserData). + +skip_varint_Override(<<1:1, _:7, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> skip_varint_Override(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +skip_varint_Override(<<0:1, _:7, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). + +skip_length_delimited_Override(<<1:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) when N < 57 -> skip_length_delimited_Override(Rest, N + 7, X bsl N + Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +skip_length_delimited_Override(<<0:1, X:7, Rest/binary>>, N, Acc, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> Length = X bsl N + Acc, <<_:Length/binary, Rest2/binary>> = Rest, - dfp_read_field_def_Override(Rest2, 0, 0, F, F@_1, F@_2, TrUserData). + dfp_read_field_def_Override(Rest2, 0, 0, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). -skip_group_Override(Bin, _, Z2, FNum, F@_1, F@_2, TrUserData) -> +skip_group_Override(Bin, _, Z2, FNum, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> {_, Rest} = read_group(Bin, FNum), - dfp_read_field_def_Override(Rest, 0, Z2, FNum, F@_1, F@_2, TrUserData). + dfp_read_field_def_Override(Rest, 0, Z2, FNum, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). -skip_32_Override(<<_:32, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData). +skip_32_Override(<<_:32, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). -skip_64_Override(<<_:64, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, TrUserData). +skip_64_Override(<<_:64, Rest/binary>>, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> dfp_read_field_def_Override(Rest, Z1, Z2, F, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData). decode_msg_Package(Bin, TrUserData) -> dfp_read_field_def_Package(Bin, 0, 0, 0, id([], TrUserData), id('$undef', TrUserData), id('$undef', TrUserData), id([], TrUserData), TrUserData). @@ -1552,6 +1612,9 @@ d_enum_Visibility(V) -> V. d_enum_OverrideAction(0) -> 'OVERRIDE_ACTION_ALLOW'; d_enum_OverrideAction(1) -> 'OVERRIDE_ACTION_DENY'; +d_enum_OverrideAction(2) -> 'OVERRIDE_ACTION_ADVISORY'; +d_enum_OverrideAction(3) -> 'OVERRIDE_ACTION_RETIREMENT'; +d_enum_OverrideAction(4) -> 'OVERRIDE_ACTION_COOLDOWN'; d_enum_OverrideAction(V) -> V. d_enum_RetirementReason(0) -> 'RETIRED_OTHER'; @@ -1705,7 +1768,23 @@ merge_msg_PackageRef(#{} = PMsg, #{package := NFpackage} = NMsg, _) -> end. -compile({nowarn_unused_function,merge_msg_Override/3}). -merge_msg_Override(#{ref := PFref}, #{action := NFaction, ref := NFref}, TrUserData) -> #{action => NFaction, ref => merge_msg_PackageRef(PFref, NFref, TrUserData)}. +merge_msg_Override(#{ref := PFref} = PMsg, #{action := NFaction, ref := NFref} = NMsg, TrUserData) -> + S1 = #{action => NFaction, ref => merge_msg_PackageRef(PFref, NFref, TrUserData)}, + S2 = case {PMsg, NMsg} of + {_, #{advisory_id := NFadvisory_id}} -> S1#{advisory_id => NFadvisory_id}; + {#{advisory_id := PFadvisory_id}, _} -> S1#{advisory_id => PFadvisory_id}; + _ -> S1 + end, + S3 = case {PMsg, NMsg} of + {_, #{retirement_reason := NFretirement_reason}} -> S2#{retirement_reason => NFretirement_reason}; + {#{retirement_reason := PFretirement_reason}, _} -> S2#{retirement_reason => PFretirement_reason}; + _ -> S2 + end, + case {PMsg, NMsg} of + {_, #{comment := NFcomment}} -> S3#{comment => NFcomment}; + {#{comment := PFcomment}, _} -> S3#{comment => PFcomment}; + _ -> S3 + end. -compile({nowarn_unused_function,merge_msg_Package/3}). merge_msg_Package(#{} = PMsg, #{name := NFname, repository := NFrepository} = NMsg, TrUserData) -> @@ -1945,8 +2024,23 @@ v_submsg_Override(Msg, Path, TrUserData) -> v_msg_Override(Msg, Path, TrUserData v_msg_Override(#{action := F1, ref := F2} = M, Path, TrUserData) -> v_enum_OverrideAction(F1, [action | Path], TrUserData), v_submsg_PackageRef(F2, [ref | Path], TrUserData), + case M of + #{advisory_id := F3} -> v_type_string(F3, [advisory_id | Path], TrUserData); + _ -> ok + end, + case M of + #{retirement_reason := F4} -> v_enum_RetirementReason(F4, [retirement_reason | Path], TrUserData); + _ -> ok + end, + case M of + #{comment := F5} -> v_type_string(F5, [comment | Path], TrUserData); + _ -> ok + end, lists:foreach(fun (action) -> ok; (ref) -> ok; + (advisory_id) -> ok; + (retirement_reason) -> ok; + (comment) -> ok; (OtherKey) -> mk_type_error({extraneous_key, OtherKey}, M, Path) end, maps:keys(M)), @@ -2153,6 +2247,9 @@ v_enum_Visibility(X, Path, _TrUserData) -> mk_type_error({invalid_enum, 'Visibil -compile({nowarn_unused_function,v_enum_OverrideAction/3}). v_enum_OverrideAction('OVERRIDE_ACTION_ALLOW', _Path, _TrUserData) -> ok; v_enum_OverrideAction('OVERRIDE_ACTION_DENY', _Path, _TrUserData) -> ok; +v_enum_OverrideAction('OVERRIDE_ACTION_ADVISORY', _Path, _TrUserData) -> ok; +v_enum_OverrideAction('OVERRIDE_ACTION_RETIREMENT', _Path, _TrUserData) -> ok; +v_enum_OverrideAction('OVERRIDE_ACTION_COOLDOWN', _Path, _TrUserData) -> ok; v_enum_OverrideAction(V, _Path, _TrUserData) when -2147483648 =< V, V =< 2147483647, is_integer(V) -> ok; v_enum_OverrideAction(X, Path, _TrUserData) -> mk_type_error({invalid_enum, 'OverrideAction'}, X, Path). @@ -2257,7 +2354,7 @@ cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. get_msg_defs() -> [{{enum, 'Visibility'}, [{'VISIBILITY_PRIVATE', 0}, {'VISIBILITY_PUBLIC', 1}]}, - {{enum, 'OverrideAction'}, [{'OVERRIDE_ACTION_ALLOW', 0}, {'OVERRIDE_ACTION_DENY', 1}]}, + {{enum, 'OverrideAction'}, [{'OVERRIDE_ACTION_ALLOW', 0}, {'OVERRIDE_ACTION_DENY', 1}, {'OVERRIDE_ACTION_ADVISORY', 2}, {'OVERRIDE_ACTION_RETIREMENT', 3}, {'OVERRIDE_ACTION_COOLDOWN', 4}]}, {{enum, 'RetirementReason'}, [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, {'RETIRED_RENAMED', 4}]}, {{enum, 'AdvisorySeverity'}, [{'SEVERITY_NONE', 0}, {'SEVERITY_LOW', 1}, {'SEVERITY_MEDIUM', 2}, {'SEVERITY_HIGH', 3}, {'SEVERITY_CRITICAL', 4}]}, {{msg, 'Policy'}, @@ -2275,7 +2372,12 @@ get_msg_defs() -> #{name => retirement_reasons, fnum => 2, rnum => 3, type => {enum, 'RetirementReason'}, occurrence => repeated, opts => [packed]}, #{name => cooldown, fnum => 3, rnum => 4, type => string, occurrence => optional, opts => []}]}, {{msg, 'PackageRef'}, [#{name => package, fnum => 1, rnum => 2, type => string, occurrence => required, opts => []}, #{name => requirement, fnum => 2, rnum => 3, type => string, occurrence => optional, opts => []}]}, - {{msg, 'Override'}, [#{name => action, fnum => 1, rnum => 2, type => {enum, 'OverrideAction'}, occurrence => required, opts => []}, #{name => ref, fnum => 2, rnum => 3, type => {msg, 'PackageRef'}, occurrence => required, opts => []}]}, + {{msg, 'Override'}, + [#{name => action, fnum => 1, rnum => 2, type => {enum, 'OverrideAction'}, occurrence => required, opts => []}, + #{name => ref, fnum => 2, rnum => 3, type => {msg, 'PackageRef'}, occurrence => required, opts => []}, + #{name => advisory_id, fnum => 3, rnum => 4, type => string, occurrence => optional, opts => []}, + #{name => retirement_reason, fnum => 4, rnum => 5, type => {enum, 'RetirementReason'}, occurrence => optional, opts => []}, + #{name => comment, fnum => 5, rnum => 6, type => string, occurrence => optional, opts => []}]}, {{msg, 'Package'}, [#{name => releases, fnum => 1, rnum => 2, type => {msg, 'Release'}, occurrence => repeated, opts => []}, #{name => name, fnum => 2, rnum => 3, type => string, occurrence => required, opts => []}, @@ -2348,7 +2450,12 @@ find_msg_def('Restriction') -> #{name => retirement_reasons, fnum => 2, rnum => 3, type => {enum, 'RetirementReason'}, occurrence => repeated, opts => [packed]}, #{name => cooldown, fnum => 3, rnum => 4, type => string, occurrence => optional, opts => []}]; find_msg_def('PackageRef') -> [#{name => package, fnum => 1, rnum => 2, type => string, occurrence => required, opts => []}, #{name => requirement, fnum => 2, rnum => 3, type => string, occurrence => optional, opts => []}]; -find_msg_def('Override') -> [#{name => action, fnum => 1, rnum => 2, type => {enum, 'OverrideAction'}, occurrence => required, opts => []}, #{name => ref, fnum => 2, rnum => 3, type => {msg, 'PackageRef'}, occurrence => required, opts => []}]; +find_msg_def('Override') -> + [#{name => action, fnum => 1, rnum => 2, type => {enum, 'OverrideAction'}, occurrence => required, opts => []}, + #{name => ref, fnum => 2, rnum => 3, type => {msg, 'PackageRef'}, occurrence => required, opts => []}, + #{name => advisory_id, fnum => 3, rnum => 4, type => string, occurrence => optional, opts => []}, + #{name => retirement_reason, fnum => 4, rnum => 5, type => {enum, 'RetirementReason'}, occurrence => optional, opts => []}, + #{name => comment, fnum => 5, rnum => 6, type => string, occurrence => optional, opts => []}]; find_msg_def('Package') -> [#{name => releases, fnum => 1, rnum => 2, type => {msg, 'Release'}, occurrence => repeated, opts => []}, #{name => name, fnum => 2, rnum => 3, type => string, occurrence => required, opts => []}, @@ -2382,7 +2489,7 @@ find_msg_def(_) -> error. find_enum_def('Visibility') -> [{'VISIBILITY_PRIVATE', 0}, {'VISIBILITY_PUBLIC', 1}]; -find_enum_def('OverrideAction') -> [{'OVERRIDE_ACTION_ALLOW', 0}, {'OVERRIDE_ACTION_DENY', 1}]; +find_enum_def('OverrideAction') -> [{'OVERRIDE_ACTION_ALLOW', 0}, {'OVERRIDE_ACTION_DENY', 1}, {'OVERRIDE_ACTION_ADVISORY', 2}, {'OVERRIDE_ACTION_RETIREMENT', 3}, {'OVERRIDE_ACTION_COOLDOWN', 4}]; find_enum_def('RetirementReason') -> [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, {'RETIRED_RENAMED', 4}]; find_enum_def('AdvisorySeverity') -> [{'SEVERITY_NONE', 0}, {'SEVERITY_LOW', 1}, {'SEVERITY_MEDIUM', 2}, {'SEVERITY_HIGH', 3}, {'SEVERITY_CRITICAL', 4}]; find_enum_def(_) -> error. @@ -2408,11 +2515,17 @@ enum_value_by_symbol_Visibility('VISIBILITY_PRIVATE') -> 0; enum_value_by_symbol_Visibility('VISIBILITY_PUBLIC') -> 1. enum_symbol_by_value_OverrideAction(0) -> 'OVERRIDE_ACTION_ALLOW'; -enum_symbol_by_value_OverrideAction(1) -> 'OVERRIDE_ACTION_DENY'. +enum_symbol_by_value_OverrideAction(1) -> 'OVERRIDE_ACTION_DENY'; +enum_symbol_by_value_OverrideAction(2) -> 'OVERRIDE_ACTION_ADVISORY'; +enum_symbol_by_value_OverrideAction(3) -> 'OVERRIDE_ACTION_RETIREMENT'; +enum_symbol_by_value_OverrideAction(4) -> 'OVERRIDE_ACTION_COOLDOWN'. enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_ALLOW') -> 0; -enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_DENY') -> 1. +enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_DENY') -> 1; +enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_ADVISORY') -> 2; +enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_RETIREMENT') -> 3; +enum_value_by_symbol_OverrideAction('OVERRIDE_ACTION_COOLDOWN') -> 4. enum_symbol_by_value_RetirementReason(0) -> 'RETIRED_OTHER'; enum_symbol_by_value_RetirementReason(1) -> 'RETIRED_INVALID'; diff --git a/test/hex_registry_SUITE.erl b/test/hex_registry_SUITE.erl index 60785dc8..af715677 100644 --- a/test/hex_registry_SUITE.erl +++ b/test/hex_registry_SUITE.erl @@ -10,7 +10,7 @@ suite() -> [{require, {ssl_certs, [test_pub, test_priv, hexpm_pub]}}]. all() -> - [names_test, versions_test, package_test, policy_test, signed_test]. + [names_test, versions_test, package_test, policy_test, policy_malformed_test, signed_test]. names_test(_Config) -> TestPublicKey = ct:get_config({ssl_certs, test_pub}), @@ -145,14 +145,33 @@ policy_test(_Config) -> overrides => [ #{ action => 'OVERRIDE_ACTION_ALLOW', - ref => #{package => <<"phoenix">>, requirement => <<"1.7.18">>} + ref => #{package => <<"phoenix">>, requirement => <<"1.7.18">>}, + comment => <<"Approved release">> }, - #{action => 'OVERRIDE_ACTION_DENY', ref => #{package => <<"foo">>}} + #{action => 'OVERRIDE_ACTION_DENY', ref => #{package => <<"foo">>}}, + #{ + action => 'OVERRIDE_ACTION_ADVISORY', + ref => #{package => <<"phoenix">>, requirement => <<"~> 1.7">>}, + advisory_id => <<"CVE-2026-0001">>, + comment => <<"Mitigated by configuration">> + }, + #{ + action => 'OVERRIDE_ACTION_RETIREMENT', + ref => #{package => <<"legacy">>}, + retirement_reason => 'RETIRED_DEPRECATED' + }, + #{ + action => 'OVERRIDE_ACTION_COOLDOWN', + ref => #{package => <<"hotfix">>, requirement => <<"== 2.0.1">>}, + comment => <<"Urgent security fix">> + } ] }, #{ repository => <<"myorg">>, - overrides => [] + overrides => [ + #{action => 'OVERRIDE_ACTION_DENY', ref => #{package => <<"old-package">>}} + ] } ] }, @@ -176,6 +195,27 @@ policy_test(_Config) -> hex_registry:unpack_policy(Payload, no_verify, no_verify, TestPublicKey), ok. +policy_malformed_test(_Config) -> + MissingRef = hex_pb_policy:decode_msg( + <<26, 13, "CVE-2026-0001">>, + 'Override' + ), + ?assertEqual( + #{action => '$undef', advisory_id => <<"CVE-2026-0001">>}, + MissingRef + ), + ?assertException( + error, + {gpb_type_error, _}, + hex_pb_policy:verify_msg(MissingRef, 'Override') + ), + ?assertException( + error, + {gpb_error, {decoding_failure, _}}, + hex_pb_policy:decode_msg(<<18, 20, "truncated">>, 'Override') + ), + ok. + signed_test(_Config) -> TestPublicKey = ct:get_config({ssl_certs, test_pub}), TestPrivateKey = ct:get_config({ssl_certs, test_priv}), diff --git a/test/hex_repo_SUITE.erl b/test/hex_repo_SUITE.erl index dfa86876..d2a4ae80 100644 --- a/test/hex_repo_SUITE.erl +++ b/test/hex_repo_SUITE.erl @@ -81,9 +81,24 @@ get_policy_test(_Config) -> overrides := [ #{ action := 'OVERRIDE_ACTION_ALLOW', - ref := #{package := <<"phoenix">>, requirement := <<"1.7.18">>} + ref := #{package := <<"phoenix">>, requirement := <<"1.7.18">>}, + comment := <<"Approved release">> }, - #{action := 'OVERRIDE_ACTION_DENY', ref := #{package := <<"foo">>}} + #{action := 'OVERRIDE_ACTION_DENY', ref := #{package := <<"foo">>}}, + #{ + action := 'OVERRIDE_ACTION_ADVISORY', + ref := #{package := <<"phoenix">>, requirement := <<"~> 1.7">>}, + advisory_id := <<"CVE-2026-0001">> + }, + #{ + action := 'OVERRIDE_ACTION_RETIREMENT', + ref := #{package := <<"legacy">>}, + retirement_reason := 'RETIRED_DEPRECATED' + }, + #{ + action := 'OVERRIDE_ACTION_COOLDOWN', + ref := #{package := <<"hotfix">>, requirement := <<"== 2.0.1">>} + } ] }, #{ diff --git a/test/support/hex_http_test.erl b/test/support/hex_http_test.erl index 01a88e76..56b134ae 100644 --- a/test/support/hex_http_test.erl +++ b/test/support/hex_http_test.erl @@ -109,9 +109,24 @@ fixture(get, <>, _, _) -> overrides => [ #{ action => 'OVERRIDE_ACTION_ALLOW', - ref => #{package => <<"phoenix">>, requirement => <<"1.7.18">>} + ref => #{package => <<"phoenix">>, requirement => <<"1.7.18">>}, + comment => <<"Approved release">> }, - #{action => 'OVERRIDE_ACTION_DENY', ref => #{package => <<"foo">>}} + #{action => 'OVERRIDE_ACTION_DENY', ref => #{package => <<"foo">>}}, + #{ + action => 'OVERRIDE_ACTION_ADVISORY', + ref => #{package => <<"phoenix">>, requirement => <<"~> 1.7">>}, + advisory_id => <<"CVE-2026-0001">> + }, + #{ + action => 'OVERRIDE_ACTION_RETIREMENT', + ref => #{package => <<"legacy">>}, + retirement_reason => 'RETIRED_DEPRECATED' + }, + #{ + action => 'OVERRIDE_ACTION_COOLDOWN', + ref => #{package => <<"hotfix">>, requirement => <<"== 2.0.1">>} + } ] }, #{