001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2020, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk.federation.trust.constraints; 019 020 021import java.util.Collections; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Objects; 025 026import net.jcip.annotations.Immutable; 027import net.minidev.json.JSONAware; 028import net.minidev.json.JSONObject; 029 030import com.nimbusds.oauth2.sdk.ParseException; 031import com.nimbusds.oauth2.sdk.util.CollectionUtils; 032import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 033import com.nimbusds.openid.connect.sdk.federation.entities.EntityID; 034 035 036/** 037 * Trust chain constraints. 038 * 039 * <p>Example JSON object: 040 * 041 * <pre> 042 * { 043 * "max_path_length" : 2, 044 * "naming_constraints" : { 045 * "permitted" : [ "https://example.com" ], 046 * "excluded" : [ "https://east.example.com" ] 047 * }, 048 * "allowed_leaf_entity_types" : [ "openid_provider", "openid_relying_party" ] 049 * } 050 * </pre> 051 * 052 * <p>Related specifications: 053 * 054 * <ul> 055 * <li>OpenID Connect Federation 1.0, section 5.2. 056 * <li>RFC 5280, section 4.2.1.10. 057 * </ul> 058 */ 059@Immutable 060public final class TrustChainConstraints implements JSONAware { 061 062 063 /** 064 * No constraint instance. 065 */ 066 public static final TrustChainConstraints NO_CONSTRAINTS = new TrustChainConstraints(); 067 068 069 /** 070 * The max path length, -1 if not specified. 071 */ 072 private final int maxPathLength; 073 074 075 /** 076 * The permitted entity IDs. 077 */ 078 private final List<EntityIDConstraint> permittedEntityIDs; 079 080 081 /** 082 * The excluded entity IDs. 083 */ 084 private final List<EntityIDConstraint> excludedEntityIDs; 085 086 087 /** 088 * The leaf entity type constraint. 089 */ 090 private final LeafEntityTypeConstraint leafEntityTypeConstraint; 091 092 093 /** 094 * Creates a new no constraints instance. 095 */ 096 public TrustChainConstraints() { 097 this(-1, null, null, LeafEntityTypeConstraint.ANY); 098 } 099 100 101 /** 102 * Creates a new trust chain constraints instance. 103 * 104 * @param maxPathLength The maximum number of entities between this and 105 * the leaf entity in the chain, -1 if not 106 * specified. 107 */ 108 public TrustChainConstraints(final int maxPathLength) { 109 this(maxPathLength, null, null, null); 110 } 111 112 113 /** 114 * Creates a new trust chain constraints instance. 115 * 116 * @param maxPathLength The maximum number of entities between 117 * this and the leaf entity in the chain, -1 118 * if not specified. 119 * @param permittedEntityIDs The permitted entity IDs, {@code null} if 120 * not specified. 121 * @param excludedEntityIDs The excluded entities, {@code null} if not 122 * specified. 123 */ 124 public TrustChainConstraints(final int maxPathLength, 125 final List<EntityIDConstraint> permittedEntityIDs, 126 final List<EntityIDConstraint> excludedEntityIDs, 127 final LeafEntityTypeConstraint leafEntityTypeConstraint) { 128 this.maxPathLength = maxPathLength; 129 this.permittedEntityIDs = permittedEntityIDs != null ? permittedEntityIDs : Collections.<EntityIDConstraint>emptyList(); 130 this.excludedEntityIDs = excludedEntityIDs != null ? excludedEntityIDs : Collections.<EntityIDConstraint>emptyList(); 131 this.leafEntityTypeConstraint = leafEntityTypeConstraint != null ? leafEntityTypeConstraint : LeafEntityTypeConstraint.ANY; 132 } 133 134 135 /** 136 * Checks if the given number of intermediates is permitted. 137 * 138 * @param numIntermediatesInPath The number of intermediate entities 139 * between the entity specifying the 140 * constraints and the specified entity. 141 * Must be zero or greater. 142 * 143 * @return {@code true} if permitted, else {@code false}. 144 */ 145 public boolean isPermitted(final int numIntermediatesInPath) { 146 147 if (numIntermediatesInPath < 0) { 148 throw new IllegalArgumentException("The path length must not be negative"); 149 } 150 151 return getMaxPathLength() <= -1 || numIntermediatesInPath <= getMaxPathLength(); 152 } 153 154 155 /** 156 * Checks if the specified entity ID is permitted. 157 * 158 * @param entityID The entity ID. Must not be {@code null}. 159 * 160 * @return {@code true} if permitted, else {@code false}. 161 */ 162 public boolean isPermitted(final EntityID entityID) { 163 164 if (getExcludedEntityIDs().isEmpty() && getPermittedEntityIDs().isEmpty()) { 165 return true; 166 } 167 168 if (! getExcludedEntityIDs().isEmpty()) { 169 170 for (EntityIDConstraint constraint: getExcludedEntityIDs()) { 171 if (constraint.matches(entityID)) { 172 return false; 173 } 174 } 175 } 176 177 if (! getPermittedEntityIDs().isEmpty()) { 178 179 for (EntityIDConstraint constraint: getPermittedEntityIDs()) { 180 if (constraint.matches(entityID)) { 181 return true; 182 } 183 } 184 } else { 185 // If passed so far - always permitted 186 return true; 187 } 188 189 return false; 190 } 191 192 193 /** 194 * Checks if the entity ID with the given number of intermediates is 195 * permitted. 196 * 197 * @param numIntermediatesInPath The number of intermediate entities 198 * between the entity specifying the 199 * constraints and the specified entity. 200 * Must be zero or greater. 201 * 202 * @param entityID The entity ID. Must not be 203 * {@code null}. 204 * 205 * @return {@code true} if allowed, else {@code false}. 206 */ 207 public boolean isPermitted(final int numIntermediatesInPath, final EntityID entityID) { 208 209 return isPermitted(numIntermediatesInPath) && isPermitted(entityID); 210 } 211 212 213 /** 214 * Returns the maximum number of entities between this and the last one 215 * in the chain. 216 * 217 * @return The maximum number of entities between this and the last one 218 * in the chain, -1 if not specified. 219 */ 220 public int getMaxPathLength() { 221 return maxPathLength; 222 } 223 224 225 /** 226 * Returns the permitted entity IDs. 227 * 228 * @return The permitted entity IDs, empty list if not specified. 229 */ 230 public List<EntityIDConstraint> getPermittedEntityIDs() { 231 return permittedEntityIDs; 232 } 233 234 235 /** 236 * Returns the excluded entity IDs. 237 * 238 * @return The excluded entity IDs, empty list if not specified. 239 */ 240 public List<EntityIDConstraint> getExcludedEntityIDs() { 241 return excludedEntityIDs; 242 } 243 244 245 /** 246 * Returns the leaf entity type constraint. 247 * 248 * @return The leaf entity type constraint. 249 */ 250 public LeafEntityTypeConstraint getLeafEntityTypeConstraint() { 251 return leafEntityTypeConstraint; 252 } 253 254 255 /** 256 * Returns a JSON object representation of this trust chain 257 * constraints. 258 * 259 * @return The JSON object. 260 */ 261 public JSONObject toJSONObject() { 262 263 JSONObject o = new JSONObject(); 264 265 if (maxPathLength > -1) { 266 o.put("max_path_length", maxPathLength); 267 } 268 269 JSONObject namingConstraints = new JSONObject(); 270 271 if (CollectionUtils.isNotEmpty(permittedEntityIDs)) { 272 List<String> vals = new LinkedList<>(); 273 for (EntityIDConstraint v: permittedEntityIDs) { 274 vals.add(v.toString()); 275 } 276 namingConstraints.put("permitted", vals); 277 } 278 279 if (CollectionUtils.isNotEmpty(excludedEntityIDs)) { 280 List<String> vals = new LinkedList<>(); 281 for (EntityIDConstraint v: excludedEntityIDs) { 282 vals.add(v.toString()); 283 } 284 namingConstraints.put("excluded", vals); 285 } 286 287 if (! namingConstraints.isEmpty()) { 288 o.put("naming_constraints", namingConstraints); 289 } 290 291 if (! leafEntityTypeConstraint.allowsAny()) { 292 o.put("allowed_leaf_entity_types", leafEntityTypeConstraint.getAllowedAsStringList()); 293 } 294 295 return o; 296 } 297 298 299 @Override 300 public String toJSONString() { 301 return toJSONObject().toJSONString(); 302 } 303 304 305 @Override 306 public boolean equals(Object o) { 307 if (this == o) return true; 308 if (!(o instanceof TrustChainConstraints)) return false; 309 TrustChainConstraints that = (TrustChainConstraints) o; 310 return getMaxPathLength() == that.getMaxPathLength() && 311 Objects.equals(getPermittedEntityIDs(), that.getPermittedEntityIDs()) && 312 Objects.equals(getExcludedEntityIDs(), that.getExcludedEntityIDs()) && 313 getLeafEntityTypeConstraint().equals(that.getLeafEntityTypeConstraint()); 314 } 315 316 317 @Override 318 public int hashCode() { 319 return Objects.hash(getMaxPathLength(), getPermittedEntityIDs(), getExcludedEntityIDs(), getLeafEntityTypeConstraint()); 320 } 321 322 323 /** 324 * Parses a trust chain constraints instance from the specified JSON 325 * object. 326 * 327 * @param jsonObject The JSON object. Must not be {@code null}. 328 * 329 * @return The trust chain constraints. 330 * 331 * @throws ParseException If parsing failed. 332 */ 333 public static TrustChainConstraints parse(final JSONObject jsonObject) 334 throws ParseException { 335 336 int maxPathLength = JSONObjectUtils.getInt(jsonObject, "max_path_length", -1); 337 338 JSONObject namingConstraints = JSONObjectUtils.getJSONObject(jsonObject, "naming_constraints", new JSONObject()); 339 340 List<EntityIDConstraint> permitted = null; 341 List<String> values = JSONObjectUtils.getStringList(namingConstraints, "permitted", null); 342 if (values != null) { 343 permitted = new LinkedList<>(); 344 for (String v: values) { 345 if (v != null) { 346 permitted.add(EntityIDConstraint.parse(v)); 347 } 348 } 349 } 350 351 List<EntityIDConstraint> excluded = null; 352 values = JSONObjectUtils.getStringList(namingConstraints, "excluded", null); 353 if (values != null) { 354 excluded = new LinkedList<>(); 355 for (String v: values) { 356 if (v != null) { 357 excluded.add(EntityIDConstraint.parse(v)); 358 } 359 } 360 } 361 362 LeafEntityTypeConstraint leafEntityTypes = LeafEntityTypeConstraint.parse( 363 JSONObjectUtils.getStringList(jsonObject, "allowed_leaf_entity_types", null) 364 ); 365 366 return new TrustChainConstraints(maxPathLength, permitted, excluded, leafEntityTypes); 367 } 368}