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.policy;
019
020
021import java.util.*;
022
023import net.minidev.json.JSONAware;
024import net.minidev.json.JSONObject;
025
026import com.nimbusds.oauth2.sdk.ParseException;
027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
028import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyOperation;
029import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException;
030import com.nimbusds.openid.connect.sdk.federation.policy.operations.DefaultPolicyOperationCombinationValidator;
031import com.nimbusds.openid.connect.sdk.federation.policy.operations.DefaultPolicyOperationFactory;
032import com.nimbusds.openid.connect.sdk.federation.policy.operations.PolicyOperationCombinationValidator;
033import com.nimbusds.openid.connect.sdk.federation.policy.operations.PolicyOperationFactory;
034
035
036/**
037 * Policy for a federation entity metadata.
038 *
039 * <p>Example:
040 *
041 * <pre>
042 * {
043 *   "scopes" : {
044 *       "subset_of"   : [ "openid", "eduperson", "phone" ],
045 *       "superset_of" : [ "openid" ],
046 *       "default"     : [ "openid", "eduperson" ]
047 *   },
048 *   "id_token_signed_response_alg" : {
049 *       "one_of" : [ "ES256", "ES384", "ES512" ]
050 *   },
051 *   "contacts" : {
052 *       "add" : "[email protected]"
053 *   },
054 *   "application_type" : { "value": "web"
055 *   }
056 * }
057 * </pre>
058 *
059 * <p>Related specifications:
060 *
061 * <ul>
062 *     <li>OpenID Connect Federation 1.0, section 4.1.
063 * </ul>
064 */
065public class MetadataPolicy implements JSONAware {
066        
067        
068        /**
069         * The policy entries, keyed by metadata parameter name.
070         */
071        private final Map<String,List<PolicyOperation>> entries = new LinkedHashMap<>();
072        
073        
074        /**
075         * Applies this policy to the specified metadata.
076         *
077         * @param metadata The metadata as JSON object. May be {@code null}.
078         *
079         * @return The resulting metadata, {@code null} if not specified.
080         *
081         * @throws PolicyViolationException On a policy violation.
082         */
083        public JSONObject apply(final JSONObject metadata)
084                throws PolicyViolationException {
085                
086                if (metadata == null) {
087                        return null;
088                }
089                
090                JSONObject out = new JSONObject();
091                
092                // Copy params not subject to policy
093                for (String key: metadata.keySet()) {
094                        if (! entries.containsKey(key)) {
095                                out.put(key, metadata.get(key));
096                        }
097                }
098                
099                // Apply policy
100                for (String key: entries.keySet()) {
101                        
102                        Object metadataValue = metadata.get(key);
103                        MetadataPolicyEntry en = getEntry(key);
104                        
105                        Object outputValue = en.apply(metadataValue);
106                        
107                        if (outputValue != null) {
108                                out.put(key, outputValue);
109                        }
110                }
111                
112                return out;
113        }
114        
115        
116        /**
117         * Puts a policy entry for a metadata parameter.
118         *
119         * @param parameterName   The parameter name. Must not be {@code null}.
120         * @param policyOperation The policy operation for the parameter,
121         *                        {@code null} if none.
122         */
123        public void put(final String parameterName, final PolicyOperation policyOperation) {
124                put(new MetadataPolicyEntry(parameterName, Collections.singletonList(policyOperation)));
125        }
126        
127        
128        /**
129         * Puts a policy entry for a metadata parameter.
130         *
131         * @param parameterName    The parameter name. Must not be {@code null}.
132         * @param policyOperations The ordered policy operations for the
133         *                         parameter, {@code null} if none.
134         */
135        public void put(final String parameterName, final List<PolicyOperation> policyOperations) {
136                put(new MetadataPolicyEntry(parameterName, policyOperations));
137        }
138        
139        
140        /**
141         * Puts a policy entry for a metadata parameter.
142         *
143         * @param entry The policy entry. Must not be {@code null}.
144         */
145        public void put(final MetadataPolicyEntry entry) {
146                entries.put(entry.getKey(), entry.getValue());
147        }
148        
149        
150        /**
151         * Gets the policy operations for the specified metadata parameter
152         * name.
153         *
154         * @param parameterName The parameter name. Must not be {@code null}.
155         *
156         * @return The ordered policy operations for the parameter,
157         *         {@code null} if none.
158         */
159        public List<PolicyOperation> get(final String parameterName) {
160
161                return entries.get(parameterName);
162        }
163        
164        
165        /**
166         * Gets the policy entry for the specified metadata parameter name.
167         *
168         * @param parameterName The parameter name. Must not be {@code null}.
169         *
170         * @return The policy entry for the parameter, {@code null} if none.
171         */
172        public MetadataPolicyEntry getEntry(final String parameterName) {
173                
174                List<PolicyOperation> policyOperations = entries.get(parameterName);
175                
176                if (policyOperations == null) {
177                        return null;
178                }
179                
180                return new MetadataPolicyEntry(parameterName, policyOperations);
181        }
182        
183        
184        /**
185         * Gets the policy entries set.
186         *
187         * @return The policy entries set.
188         */
189        public Set<MetadataPolicyEntry> entrySet() {
190                
191                Set<MetadataPolicyEntry> set = new LinkedHashSet<>();
192                for (Map.Entry<String,List<PolicyOperation>> en: entries.entrySet()) {
193                        set.add(new MetadataPolicyEntry(en.getKey(), en.getValue()));
194                }
195                return set;
196        }
197        
198        
199        /**
200         * Removes a policy entry.
201         *
202         * @param parameterName The parameter name. Must not be {@code null}.
203         *
204         * @return The ordered policy operations for the removed parameter,
205         *         {@code null} if not found.
206         */
207        public List<PolicyOperation> remove(final String parameterName) {
208                
209                return entries.remove(parameterName);
210        }
211        
212        
213        /**
214         * Returns a JSON object representation of this metadata policy.
215         *
216         * @return The JSON object.
217         */
218        public JSONObject toJSONObject() {
219                
220                JSONObject jsonObject = new JSONObject();
221                
222                for (MetadataPolicyEntry en: entrySet()) {
223                        JSONObject policyEntryJSONObject = en.toJSONObject();
224                        if (policyEntryJSONObject == null) {
225                                continue; // skip
226                        }
227                        jsonObject.put(en.getKey(), en.toJSONObject());
228                }
229                
230                return jsonObject;
231        }
232        
233        
234        @Override
235        public String toJSONString() {
236                return toJSONObject().toJSONString();
237        }
238        
239        
240        @Override
241        public String toString() {
242                return toJSONString();
243        }
244        
245        
246        /**
247         * Combines the specified list of metadata policies. Uses the
248         * {@link DefaultPolicyOperationCombinationValidator default policy
249         * combination validator}.
250         *
251         * @param policies The metadata policies. Must not be empty or
252         *                 {@code null}.
253         *
254         * @return The new combined metadata policy.
255         *
256         * @throws PolicyViolationException On a policy violation.
257         */
258        public static MetadataPolicy combine(final List<MetadataPolicy> policies)
259                throws PolicyViolationException {
260                
261                return combine(policies, MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
262        }
263        
264        
265        /**
266         * Combines the specified list of metadata policies.
267         *
268         * @param policies             The metadata policies. Must not be empty
269         *                             or {@code null}.
270         * @param combinationValidator The policy operation combination
271         *                             validator. Must not be {@code null}.
272         *
273         * @return The new combined metadata policy.
274         *
275         * @throws PolicyViolationException On a policy violation.
276         */
277        public static MetadataPolicy combine(final List<MetadataPolicy> policies,
278                                             final PolicyOperationCombinationValidator combinationValidator)
279                throws PolicyViolationException {
280                
281                MetadataPolicy out = new MetadataPolicy();
282                
283                for (MetadataPolicy p: policies) {
284                        for (MetadataPolicyEntry entry: p.entrySet()) {
285                                MetadataPolicyEntry existingEntry = out.getEntry(entry.getParameterName());
286                                if (existingEntry == null) {
287                                        // add
288                                        out.put(entry);
289                                } else {
290                                        // merge
291                                        out.put(existingEntry.combine(entry, combinationValidator));
292                                }
293                        }
294                }
295                
296                return out;
297        }
298        
299        
300        /**
301         * Parses a policy for a federation entity metadata. This method is
302         * intended for policies with standard {@link PolicyOperation}s only.
303         * Uses the default {@link DefaultPolicyOperationFactory policy
304         * operation} and {@link DefaultPolicyOperationCombinationValidator
305         * policy combination validator} factories.
306         *
307         * @param policySpec The JSON object string for the policy
308         *                   specification. Must not be {@code null}.
309         *
310         * @return The metadata policy.
311         *
312         * @throws ParseException           On JSON parsing exception.
313         * @throws PolicyViolationException On a policy violation.
314         */
315        public static MetadataPolicy parse(final JSONObject policySpec)
316                throws ParseException, PolicyViolationException {
317                
318                return parse(policySpec,
319                        MetadataPolicyEntry.DEFAULT_POLICY_OPERATION_FACTORY,
320                        MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
321        }
322        
323        
324        /**
325         * Parses a policy for a federation entity metadata. This method is
326         * intended for policies including non-standard
327         * {@link PolicyOperation}s.
328         *
329         * @param policySpec           The JSON object for the policy
330         *                             specification. Must not be {@code null}.
331         * @param factory              The policy operation factory. Must not
332         *                             be {@code null}.
333         * @param combinationValidator The policy operation combination
334         *                             validator. Must not be {@code null}.
335         *
336         * @return The metadata policy.
337         *
338         * @throws ParseException           On JSON parsing exception.
339         * @throws PolicyViolationException On a policy violation.
340         */
341        public static MetadataPolicy parse(final JSONObject policySpec,
342                                           final PolicyOperationFactory factory,
343                                           final PolicyOperationCombinationValidator combinationValidator)
344                throws ParseException, PolicyViolationException {
345                
346                MetadataPolicy metadataPolicy = new MetadataPolicy();
347                
348                for (String parameterName: policySpec.keySet()) {
349                        JSONObject entrySpec = JSONObjectUtils.getJSONObject(policySpec, parameterName);
350                        metadataPolicy.put(MetadataPolicyEntry.parse(parameterName, entrySpec, factory, combinationValidator));
351                }
352                
353                return metadataPolicy;
354        }
355        
356        
357        /**
358         * Parses a policy for a federation entity metadata. This method is
359         * intended for policies with standard {@link PolicyOperation}s only.
360         * Uses the default {@link DefaultPolicyOperationFactory policy
361         * operation} and {@link DefaultPolicyOperationCombinationValidator
362         * policy combination validator} factories.
363         *
364         * @param policySpec The JSON object string for the policy
365         *                   specification. Must not be {@code null}.
366         *
367         * @return The metadata policy.
368         *
369         * @throws ParseException           On JSON parsing exception.
370         * @throws PolicyViolationException On a policy violation.
371         */
372        public static MetadataPolicy parse(final String policySpec)
373                throws ParseException, PolicyViolationException {
374                
375                return parse(policySpec,
376                        MetadataPolicyEntry.DEFAULT_POLICY_OPERATION_FACTORY,
377                        MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
378        }
379        
380        
381        /**
382         * Parses a policy for a federation entity metadata. This method is
383         * intended for policies including non-standard
384         * {@link PolicyOperation}s.
385         *
386         * @param policySpec           The JSON object for the policy
387         *                             specification. Must not be {@code null}.
388         * @param factory              The policy operation factory. Must not
389         *                             be {@code null}.
390         * @param combinationValidator The policy operation combination
391         *                             validator. Must not be {@code null}.
392         *
393         * @return The metadata policy.
394         *
395         * @throws ParseException           On JSON parsing exception.
396         * @throws PolicyViolationException On a policy violation.
397         */
398        public static MetadataPolicy parse(final String policySpec,
399                                           final PolicyOperationFactory factory,
400                                           final PolicyOperationCombinationValidator combinationValidator)
401                throws ParseException, PolicyViolationException {
402                
403                return parse(JSONObjectUtils.parse(policySpec), factory, combinationValidator);
404        }
405}