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}