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 117 * between this and the leaf entity in 118 * the chain, -1 if not specified. 119 * @param permittedEntityIDs The permitted entity IDs, 120 * {@code null} if not specified. 121 * @param excludedEntityIDs The excluded entities, {@code null} 122 * if not specified. 123 * @param leafEntityTypeConstraint The leaf entity type constraint, 124 * {@code null} if not specified. 125 */ 126 public TrustChainConstraints(final int maxPathLength, 127 final List<EntityIDConstraint> permittedEntityIDs, 128 final List<EntityIDConstraint> excludedEntityIDs, 129 final LeafEntityTypeConstraint leafEntityTypeConstraint) { 130 this.maxPathLength = maxPathLength; 131 this.permittedEntityIDs = permittedEntityIDs != null ? permittedEntityIDs : Collections.<EntityIDConstraint>emptyList(); 132 this.excludedEntityIDs = excludedEntityIDs != null ? excludedEntityIDs : Collections.<EntityIDConstraint>emptyList(); 133 this.leafEntityTypeConstraint = leafEntityTypeConstraint != null ? leafEntityTypeConstraint : LeafEntityTypeConstraint.ANY; 134 } 135 136 137 /** 138 * Checks if the given number of intermediates is permitted. 139 * 140 * @param numIntermediatesInPath The number of intermediate entities 141 * between the entity specifying the 142 * constraints and the specified entity. 143 * Must be zero or greater. 144 * 145 * @return {@code true} if permitted, else {@code false}. 146 */ 147 public boolean isPermitted(final int numIntermediatesInPath) { 148 149 if (numIntermediatesInPath < 0) { 150 throw new IllegalArgumentException("The path length must not be negative"); 151 } 152 153 return getMaxPathLength() <= -1 || numIntermediatesInPath <= getMaxPathLength(); 154 } 155 156 157 /** 158 * Checks if the specified entity ID is permitted. 159 * 160 * @param entityID The entity ID. Must not be {@code null}. 161 * 162 * @return {@code true} if permitted, else {@code false}. 163 */ 164 public boolean isPermitted(final EntityID entityID) { 165 166 if (getExcludedEntityIDs().isEmpty() && getPermittedEntityIDs().isEmpty()) { 167 return true; 168 } 169 170 if (! getExcludedEntityIDs().isEmpty()) { 171 172 for (EntityIDConstraint constraint: getExcludedEntityIDs()) { 173 if (constraint.matches(entityID)) { 174 return false; 175 } 176 } 177 } 178 179 if (! getPermittedEntityIDs().isEmpty()) { 180 181 for (EntityIDConstraint constraint: getPermittedEntityIDs()) { 182 if (constraint.matches(entityID)) { 183 return true; 184 } 185 } 186 } else { 187 // If passed so far - always permitted 188 return true; 189 } 190 191 return false; 192 } 193 194 195 /** 196 * Checks if the entity ID with the given number of intermediates is 197 * permitted. 198 * 199 * @param numIntermediatesInPath The number of intermediate entities 200 * between the entity specifying the 201 * constraints and the specified entity. 202 * Must be zero or greater. 203 * 204 * @param entityID The entity ID. Must not be 205 * {@code null}. 206 * 207 * @return {@code true} if allowed, else {@code false}. 208 */ 209 public boolean isPermitted(final int numIntermediatesInPath, final EntityID entityID) { 210 211 return isPermitted(numIntermediatesInPath) && isPermitted(entityID); 212 } 213 214 215 /** 216 * Returns the maximum number of entities between this and the last one 217 * in the chain. 218 * 219 * @return The maximum number of entities between this and the last one 220 * in the chain, -1 if not specified. 221 */ 222 public int getMaxPathLength() { 223 return maxPathLength; 224 } 225 226 227 /** 228 * Returns the permitted entity IDs. 229 * 230 * @return The permitted entity IDs, empty list if not specified. 231 */ 232 public List<EntityIDConstraint> getPermittedEntityIDs() { 233 return permittedEntityIDs; 234 } 235 236 237 /** 238 * Returns the excluded entity IDs. 239 * 240 * @return The excluded entity IDs, empty list if not specified. 241 */ 242 public List<EntityIDConstraint> getExcludedEntityIDs() { 243 return excludedEntityIDs; 244 } 245 246 247 /** 248 * Returns the leaf entity type constraint. 249 * 250 * @return The leaf entity type constraint. 251 */ 252 public LeafEntityTypeConstraint getLeafEntityTypeConstraint() { 253 return leafEntityTypeConstraint; 254 } 255 256 257 /** 258 * Returns a JSON object representation of this trust chain 259 * constraints. 260 * 261 * @return The JSON object. 262 */ 263 public JSONObject toJSONObject() { 264 265 JSONObject o = new JSONObject(); 266 267 if (maxPathLength > -1) { 268 o.put("max_path_length", maxPathLength); 269 } 270 271 JSONObject namingConstraints = new JSONObject(); 272 273 if (CollectionUtils.isNotEmpty(permittedEntityIDs)) { 274 List<String> vals = new LinkedList<>(); 275 for (EntityIDConstraint v: permittedEntityIDs) { 276 vals.add(v.toString()); 277 } 278 namingConstraints.put("permitted", vals); 279 } 280 281 if (CollectionUtils.isNotEmpty(excludedEntityIDs)) { 282 List<String> vals = new LinkedList<>(); 283 for (EntityIDConstraint v: excludedEntityIDs) { 284 vals.add(v.toString()); 285 } 286 namingConstraints.put("excluded", vals); 287 } 288 289 if (! namingConstraints.isEmpty()) { 290 o.put("naming_constraints", namingConstraints); 291 } 292 293 if (! leafEntityTypeConstraint.allowsAny()) { 294 o.put("allowed_leaf_entity_types", leafEntityTypeConstraint.getAllowedAsStringList()); 295 } 296 297 return o; 298 } 299 300 301 @Override 302 public String toJSONString() { 303 return toJSONObject().toJSONString(); 304 } 305 306 307 @Override 308 public boolean equals(Object o) { 309 if (this == o) return true; 310 if (!(o instanceof TrustChainConstraints)) return false; 311 TrustChainConstraints that = (TrustChainConstraints) o; 312 return getMaxPathLength() == that.getMaxPathLength() && 313 Objects.equals(getPermittedEntityIDs(), that.getPermittedEntityIDs()) && 314 Objects.equals(getExcludedEntityIDs(), that.getExcludedEntityIDs()) && 315 getLeafEntityTypeConstraint().equals(that.getLeafEntityTypeConstraint()); 316 } 317 318 319 @Override 320 public int hashCode() { 321 return Objects.hash(getMaxPathLength(), getPermittedEntityIDs(), getExcludedEntityIDs(), getLeafEntityTypeConstraint()); 322 } 323 324 325 /** 326 * Parses a trust chain constraints instance from the specified JSON 327 * object. 328 * 329 * @param jsonObject The JSON object. Must not be {@code null}. 330 * 331 * @return The trust chain constraints. 332 * 333 * @throws ParseException If parsing failed. 334 */ 335 public static TrustChainConstraints parse(final JSONObject jsonObject) 336 throws ParseException { 337 338 int maxPathLength = JSONObjectUtils.getInt(jsonObject, "max_path_length", -1); 339 340 JSONObject namingConstraints = JSONObjectUtils.getJSONObject(jsonObject, "naming_constraints", new JSONObject()); 341 342 List<EntityIDConstraint> permitted = null; 343 List<String> values = JSONObjectUtils.getStringList(namingConstraints, "permitted", null); 344 if (values != null) { 345 permitted = new LinkedList<>(); 346 for (String v: values) { 347 if (v != null) { 348 permitted.add(EntityIDConstraint.parse(v)); 349 } 350 } 351 } 352 353 List<EntityIDConstraint> excluded = null; 354 values = JSONObjectUtils.getStringList(namingConstraints, "excluded", null); 355 if (values != null) { 356 excluded = new LinkedList<>(); 357 for (String v: values) { 358 if (v != null) { 359 excluded.add(EntityIDConstraint.parse(v)); 360 } 361 } 362 } 363 364 LeafEntityTypeConstraint leafEntityTypes = LeafEntityTypeConstraint.parse( 365 JSONObjectUtils.getStringList(jsonObject, "allowed_leaf_entity_types", null) 366 ); 367 368 return new TrustChainConstraints(maxPathLength, permitted, excluded, leafEntityTypes); 369 } 370}