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