2525import java .nio .file .Files ;
2626import java .nio .file .Path ;
2727import java .util .Collections ;
28+ import java .util .LinkedHashMap ;
2829import java .util .List ;
2930import java .util .Map ;
3031import land .oras .OrasModel ;
4748 * verification is required.
4849 *
4950 * @see PolicyRequirement
50- * @see SignedIdentity
5151 */
5252@ NullMarked
5353public class ContainersPolicy {
5454
5555 private static final Logger LOG = LoggerFactory .getLogger (ContainersPolicy .class );
5656
5757 /**
58- * A dedicated Jackson mapper for policy.json that supports {@link PolicyRequirement} and
59- * {@link SignedIdentity} polymorphic deserialization.
58+ * A dedicated Jackson mapper for policy.json that supports {@link PolicyRequirement}
59+ * polymorphic deserialization.
6060 *
6161 * <p>The global mapper in {@link JsonUtils} has a {@code NON_EMPTY} global inclusion filter
6262 * that would interfere with the {@code @JsonTypeInfo} resolution here, so we use a separate
@@ -148,26 +148,26 @@ public static ContainersPolicy rejectAll() {
148148 * operation to proceed here; their cryptographic check runs in {@link #verify(PolicyContext)} once
149149 * the image has been resolved during a pull.
150150 *
151- * @param transport the transport name , e.g. {@code "docker" }.
151+ * @param transport the transport, e.g. {@link Transport#DOCKER }.
152152 * @param scope the image scope, e.g. {@code "docker.io/library/nginx"}.
153153 * @return {@code true} if all resolved requirements pass.
154154 */
155- public boolean isAllowed (String transport , String scope ) {
155+ public boolean isAllowed (Transport transport , String scope ) {
156156 PolicyContext context = PolicyContext .forScope (transport , scope );
157157 List <PolicyRequirement > requirements = resolveRequirements (transport , scope );
158158 for (PolicyRequirement req : requirements ) {
159159 if (!req .verify (context )) {
160- LOG .debug ("Policy requirement {} failed for transport='{}' scope='{}' " , req , transport , scope );
160+ LOG .debug ("Policy requirement {} failed for transport {} and scope {} " , req , transport , scope );
161161 return false ;
162162 }
163163 }
164- LOG .debug ("Policy all requirements passed for transport='{}' scope='{}' " , transport , scope );
164+ LOG .debug ("Policy all requirements passed for transport {} and scope {} " , transport , scope );
165165 return true ;
166166 }
167167
168168 /**
169169 * Verify a resolved image against this policy, performing content-based checks (such as Sigstore
170- * signature verification) that {@link #isAllowed(String , String)} cannot perform.
170+ * signature verification) that {@link #isAllowed(Transport , String)} cannot perform.
171171 *
172172 * <p>All resolved requirements must pass (logical AND). If any requirement fails, an
173173 * {@link OrasException} is thrown describing the failure.
@@ -190,18 +190,18 @@ public void verify(PolicyContext context) {
190190 * Resolve the list of {@link PolicyRequirement} objects that apply to the given transport and
191191 * scope, following the precedence rules described in {@link #isAllowed}.
192192 *
193- * @param transport the transport name , e.g. {@code "docker" }.
193+ * @param transport the transport, e.g. {@link Transport#DOCKER }.
194194 * @param scope the image scope, e.g. {@code "docker.io/library/nginx"}.
195195 * @return the non-null, possibly empty list of requirements (empty means global default
196196 * was used and it too was empty — treat as reject-by-default for safety).
197197 */
198- public List <PolicyRequirement > resolveRequirements (String transport , String scope ) {
198+ public List <PolicyRequirement > resolveRequirements (Transport transport , String scope ) {
199199 Map <String , List <PolicyRequirement >> transportMap =
200200 policyFile .transports ().getOrDefault (transport , Collections .emptyMap ());
201201
202202 // Exact match
203203 if (transportMap .containsKey (scope )) {
204- LOG .debug ("Policy: exact match for transport='{}' scope='{}' " , transport , scope );
204+ LOG .debug ("Policy: exact match for transport {} and scope {} " , transport , scope );
205205 return transportMap .get (scope );
206206 }
207207
@@ -216,7 +216,7 @@ public List<PolicyRequirement> resolveRequirements(String transport, String scop
216216 }
217217 }
218218 if (best != null ) {
219- LOG .debug ("Policy: prefix match '{}' for transport='{}' scope='{}' " , best , transport , scope );
219+ LOG .debug ("Policy: prefix match '{}' for transport {} and scope {} " , best , transport , scope );
220220 return transportMap .get (best );
221221 }
222222
@@ -230,18 +230,18 @@ public List<PolicyRequirement> resolveRequirements(String transport, String scop
230230 }
231231 }
232232 if (bestWildcard != null ) {
233- LOG .debug ("Policy: wildcard match '{}' for transport='{}' scope='{}' " , bestWildcard , transport , scope );
233+ LOG .debug ("Policy: wildcard match '{}' for transport {} and scope {} " , bestWildcard , transport , scope );
234234 return transportMap .get (bestWildcard );
235235 }
236236
237237 // Transport default
238238 if (transportMap .containsKey ("" )) {
239- LOG .debug ("Policy: transport default for transport='{}' " , transport );
239+ LOG .debug ("Policy: transport default for transport {} " , transport );
240240 return transportMap .get ("" );
241241 }
242242
243243 // Default
244- LOG .debug ("Policy: global default for transport='{}' scope='{}' " , transport , scope );
244+ LOG .debug ("Policy: global default for transport {} and scope {} " , transport , scope );
245245 return policyFile .defaultRequirements ();
246246 }
247247
@@ -257,9 +257,9 @@ public List<PolicyRequirement> getDefaultRequirements() {
257257 /**
258258 * Return all transport-scoped requirements as an unmodifiable map.
259259 *
260- * @return a map from transport name to a map of scope → requirements.
260+ * @return a map from {@link Transport} to a map of scope → requirements.
261261 */
262- public Map <String , Map <String , List <PolicyRequirement >>> getTransports () {
262+ public Map <Transport , Map <String , List <PolicyRequirement >>> getTransports () {
263263 return Collections .unmodifiableMap (policyFile .transports ());
264264 }
265265
@@ -318,21 +318,30 @@ private static List<Path> defaultPolicyPaths() {
318318 */
319319 @ OrasModel
320320 record PolicyFile (
321- @ JsonProperty ( "default" ) List <PolicyRequirement > defaultRequirements ,
322- @ JsonProperty ( "transports" ) Map <String , Map <String , List <PolicyRequirement >>> transports ) {
321+ List <PolicyRequirement > defaultRequirements ,
322+ Map <Transport , Map <String , List <PolicyRequirement >>> transports ) {
323323
324324 /**
325- * Creates a new {@link PolicyFile}.
325+ * Deserialize a {@link PolicyFile} from its JSON form, mapping the raw transport keys to the
326+ * {@link Transport} enum (any non-{@code docker} transport is merged into {@link Transport#UNKNOWN}).
326327 *
327- * @param defaultRequirements the global default requirements.
328- * @param transports the per-transport requirements.
328+ * @param defaultRequirements the global default requirements (key {@code "default"}).
329+ * @param rawTransports the per-transport requirements keyed by raw transport name.
330+ * @return the parsed policy file.
329331 */
330332 @ JsonCreator
331- PolicyFile (
333+ static PolicyFile fromJson (
332334 @ JsonProperty ("default" ) @ Nullable List <PolicyRequirement > defaultRequirements ,
333- @ JsonProperty ("transports" ) @ Nullable Map <String , Map <String , List <PolicyRequirement >>> transports ) {
334- this .defaultRequirements = defaultRequirements != null ? defaultRequirements : Collections .emptyList ();
335- this .transports = transports != null ? transports : Collections .emptyMap ();
335+ @ JsonProperty ("transports" ) @ Nullable Map <String , Map <String , List <PolicyRequirement >>> rawTransports ) {
336+ List <PolicyRequirement > defaults =
337+ defaultRequirements != null ? defaultRequirements : Collections .emptyList ();
338+ Map <Transport , Map <String , List <PolicyRequirement >>> byTransport = new LinkedHashMap <>();
339+ if (rawTransports != null ) {
340+ rawTransports .forEach ((name , scopes ) -> byTransport
341+ .computeIfAbsent (Transport .fromValue (name ), t -> new LinkedHashMap <>())
342+ .putAll (scopes ));
343+ }
344+ return new PolicyFile (defaults , byTransport );
336345 }
337346 }
338347}
0 commit comments