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 * } 049 * </pre> 050 * 051 * <p>Related specifications: 052 * 053 * <ul> 054 * <li>OpenID Connect Federation 1.0, section 7.2. 055 * <li>RFC 5280, section 4.2.1.10. 056 * </ul> 057 */ 058@Immutable 059public final class TrustChainConstraints implements JSONAware { 060 061 062 /** 063 * No constraint instance. 064 */ 065 public static final TrustChainConstraints NO_CONSTRAINTS = new TrustChainConstraints(); 066 067 068 /** 069 * The max path length, -1 if not specified. 070 */ 071 private final int maxPathLength; 072 073 074 /** 075 * The permitted entities. 076 */ 077 private final List<EntityIDConstraint> permittedEntities; 078 079 080 /** 081 * The excluded entities. 082 */ 083 private final List<EntityIDConstraint> excludedEntities; 084 085 086 /** 087 * Creates a new no constraints instance. 088 */ 089 public TrustChainConstraints() { 090 this(-1, null, null); 091 } 092 093 094 /** 095 * Creates a new trust chain constraints instance. 096 * 097 * @param maxPathLength The maximum number of entities between this and 098 * the last one in the chain, -1 if not specified. 099 */ 100 public TrustChainConstraints(final int maxPathLength) { 101 this(maxPathLength, null, null); 102 } 103 104 105 /** 106 * Creates a new trust chain constraints instance. 107 * 108 * @param maxPathLength The maximum number of entities between this 109 * and the last one in the chain, -1 if not 110 * specified. 111 * @param permittedEntities The permitted entities, {@code null} if not 112 * specified. 113 * @param excludedEntities The excluded entities, {@code null} if not 114 * specified. 115 */ 116 public TrustChainConstraints(final int maxPathLength, final List<EntityIDConstraint> permittedEntities, final List<EntityIDConstraint> excludedEntities) { 117 this.maxPathLength = maxPathLength; 118 this.permittedEntities = permittedEntities != null ? permittedEntities : Collections.<EntityIDConstraint>emptyList(); 119 this.excludedEntities = excludedEntities != null ? excludedEntities : Collections.<EntityIDConstraint>emptyList(); 120 } 121 122 123 /** 124 * Checks if the given number of intermediates is permitted. 125 * 126 * @param numIntermediatesInPath The number of intermediate entities 127 * between the entity specifying the 128 * constraints and the specified entity. 129 * Must be zero or greater. 130 * 131 * @return {@code true} if permitted, else {@code false}. 132 */ 133 public boolean isPermitted(final int numIntermediatesInPath) { 134 135 if (numIntermediatesInPath < 0) { 136 throw new IllegalArgumentException("The path length must not be negative"); 137 } 138 139 return getMaxPathLength() <= -1 || numIntermediatesInPath <= getMaxPathLength(); 140 } 141 142 143 /** 144 * Checks if the entity ID is permitted. 145 * 146 * @param entityID The entity ID. Must not be {@code null}. 147 * 148 * @return {@code true} if permitted, else {@code false}. 149 */ 150 public boolean isPermitted(final EntityID entityID) { 151 152 if (getExcludedEntities().isEmpty() && getPermittedEntities().isEmpty()) { 153 return true; 154 } 155 156 if (! getExcludedEntities().isEmpty()) { 157 158 for (EntityIDConstraint constraint: getExcludedEntities()) { 159 if (constraint.matches(entityID)) { 160 return false; 161 } 162 } 163 } 164 165 if (! getPermittedEntities().isEmpty()) { 166 167 for (EntityIDConstraint constraint: getPermittedEntities()) { 168 if (constraint.matches(entityID)) { 169 return true; 170 } 171 } 172 } else { 173 // If passed so far - always permitted 174 return true; 175 } 176 177 return false; 178 } 179 180 181 /** 182 * Checks if the entity ID with the given number of intermediates is 183 * permitted. 184 * 185 * @param numIntermediatesInPath The number of intermediate entities 186 * between the entity specifying the 187 * constraints and the specified entity. 188 * Must be zero or greater. 189 * 190 * @param entityID The entity ID. Must not be 191 * {@code null}. 192 * @return {@code true} if permitted, else {@code false}. 193 */ 194 public boolean isPermitted(final int numIntermediatesInPath, final EntityID entityID) { 195 196 return isPermitted(numIntermediatesInPath) && isPermitted(entityID); 197 } 198 199 200 /** 201 * Returns the maximum number of entities between this and the last one 202 * in the chain. 203 * 204 * @return The maximum number of entities between this and the last one 205 * in the chain, -1 if not specified. 206 */ 207 public int getMaxPathLength() { 208 return maxPathLength; 209 } 210 211 212 /** 213 * Returns the permitted entities. 214 * 215 * @return The permitted entities, empty list if not specified. 216 */ 217 public List<EntityIDConstraint> getPermittedEntities() { 218 return permittedEntities; 219 } 220 221 222 /** 223 * Returns the excluded entities. 224 * 225 * @return The excluded entities, empty list if not specified. 226 */ 227 public List<EntityIDConstraint> getExcludedEntities() { 228 return excludedEntities; 229 } 230 231 232 /** 233 * Returns a JSON object representation of this trust chain 234 * constraints. 235 * 236 * @return The JSON object. 237 */ 238 public JSONObject toJSONObject() { 239 240 JSONObject o = new JSONObject(); 241 242 if (maxPathLength > -1) { 243 o.put("max_path_length", maxPathLength); 244 } 245 246 JSONObject namingConstraints = new JSONObject(); 247 248 if (CollectionUtils.isNotEmpty(permittedEntities)) { 249 List<String> vals = new LinkedList<>(); 250 for (EntityIDConstraint v: permittedEntities) { 251 vals.add(v.toString()); 252 } 253 namingConstraints.put("permitted", vals); 254 } 255 256 if (CollectionUtils.isNotEmpty(excludedEntities)) { 257 List<String> vals = new LinkedList<>(); 258 for (EntityIDConstraint v: excludedEntities) { 259 vals.add(v.toString()); 260 } 261 namingConstraints.put("excluded", vals); 262 } 263 264 if (! namingConstraints.isEmpty()) { 265 o.put("naming_constraints", namingConstraints); 266 } 267 268 return o; 269 } 270 271 272 @Override 273 public String toJSONString() { 274 return toJSONObject().toJSONString(); 275 } 276 277 278 @Override 279 public boolean equals(Object o) { 280 if (this == o) return true; 281 if (!(o instanceof TrustChainConstraints)) return false; 282 TrustChainConstraints that = (TrustChainConstraints) o; 283 return getMaxPathLength() == that.getMaxPathLength() && 284 Objects.equals(getPermittedEntities(), that.getPermittedEntities()) && 285 Objects.equals(getExcludedEntities(), that.getExcludedEntities()); 286 } 287 288 289 @Override 290 public int hashCode() { 291 return Objects.hash(getMaxPathLength(), getPermittedEntities(), getExcludedEntities()); 292 } 293 294 295 /** 296 * Parses a trust chain constraints instance from the specified JSON 297 * object. 298 * 299 * @param jsonObject The JSON object. Must not be {@code null}. 300 * 301 * @return The trust chain constraints. 302 * 303 * @throws ParseException If parsing failed. 304 */ 305 public static TrustChainConstraints parse(final JSONObject jsonObject) 306 throws ParseException { 307 308 int maxPathLength = JSONObjectUtils.getInt(jsonObject, "max_path_length", -1); 309 310 JSONObject namingConstraints = JSONObjectUtils.getJSONObject(jsonObject, "naming_constraints", new JSONObject()); 311 312 List<EntityIDConstraint> permitted = null; 313 List<String> values = JSONObjectUtils.getStringList(namingConstraints, "permitted", null); 314 if (values != null) { 315 permitted = new LinkedList<>(); 316 for (String v: values) { 317 if (v != null) { 318 permitted.add(EntityIDConstraint.parse(v)); 319 } 320 } 321 } 322 323 List<EntityIDConstraint> excluded = null; 324 values = JSONObjectUtils.getStringList(namingConstraints, "excluded", null); 325 if (values != null) { 326 excluded = new LinkedList<>(); 327 for (String v: values) { 328 if (v != null) { 329 excluded.add(EntityIDConstraint.parse(v)); 330 } 331 } 332 } 333 334 return new TrustChainConstraints(maxPathLength, permitted, excluded); 335 } 336}