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" : "helpdesk@federation.example.org"
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 by 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.put(en.getKey(), en.toJSONObject());
224                }
225                
226                return jsonObject;
227        }
228        
229        
230        @Override
231        public String toJSONString() {
232                return toJSONObject().toJSONString();
233        }
234        
235        
236        /**
237         * Combines the specified list of metadata policies. Uses the
238         * {@link DefaultPolicyOperationCombinationValidator default policy
239         * combination validator}.
240         *
241         * @param policies The metadata policies. Must not be empty or
242         *                 {@code null}.
243         *
244         * @return The new combined metadata policy.
245         *
246         * @throws PolicyViolationException On a policy violation.
247         */
248        public static MetadataPolicy combine(final List<MetadataPolicy> policies)
249                throws PolicyViolationException {
250                
251                return combine(policies, MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
252        }
253        
254        
255        /**
256         * Combines the specified list of metadata policies.
257         *
258         * @param policies             The metadata policies. Must not be empty
259         *                             or {@code null}.
260         * @param combinationValidator The policy operation combination
261         *                             validator. Must not be {@code null}.
262         *
263         * @return The new combined metadata policy.
264         *
265         * @throws PolicyViolationException On a policy violation.
266         */
267        public static MetadataPolicy combine(final List<MetadataPolicy> policies,
268                                             final PolicyOperationCombinationValidator combinationValidator)
269                throws PolicyViolationException {
270                
271                MetadataPolicy out = new MetadataPolicy();
272                
273                for (MetadataPolicy p: policies) {
274                        for (MetadataPolicyEntry entry: p.entrySet()) {
275                                MetadataPolicyEntry existingEntry = out.getEntry(entry.getParameterName());
276                                if (existingEntry == null) {
277                                        // add
278                                        out.put(entry);
279                                } else {
280                                        // merge
281                                        out.put(existingEntry.combine(entry, combinationValidator));
282                                }
283                        }
284                }
285                
286                return out;
287        }
288        
289        
290        /**
291         * Parses a policy for a federation entity metadata. This method is
292         * intended for policies with standard {@link PolicyOperation}s only.
293         * Uses the default {@link DefaultPolicyOperationFactory policy
294         * operation} and {@link DefaultPolicyOperationCombinationValidator
295         * policy combination validator} factories.
296         *
297         * @param policySpec The JSON object string for the policy
298         *                   specification. Must not be {@code null}.
299         *
300         * @return The metadata policy.
301         *
302         * @throws ParseException           On JSON parsing exception.
303         * @throws PolicyViolationException On a policy violation.
304         */
305        public static MetadataPolicy parse(final JSONObject policySpec)
306                throws ParseException, PolicyViolationException {
307                
308                return parse(policySpec,
309                        MetadataPolicyEntry.DEFAULT_POLICY_OPERATION_FACTORY,
310                        MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
311        }
312        
313        
314        /**
315         * Parses a policy for a federation entity metadata. This method is
316         * intended for policies including non-standard
317         * {@link PolicyOperation}s.
318         *
319         * @param policySpec           The JSON object for the policy
320         *                             specification. Must not be {@code null}.
321         * @param factory              The policy operation factory. Must not
322         *                             be {@code null}.
323         * @param combinationValidator The policy operation combination
324         *                             validator. Must not be {@code null}.
325         *
326         * @return The metadata policy.
327         *
328         * @throws ParseException           On JSON parsing exception.
329         * @throws PolicyViolationException On a policy violation.
330         */
331        public static MetadataPolicy parse(final JSONObject policySpec,
332                                           final PolicyOperationFactory factory,
333                                           final PolicyOperationCombinationValidator combinationValidator)
334                throws ParseException, PolicyViolationException {
335                
336                MetadataPolicy metadataPolicy = new MetadataPolicy();
337                
338                for (String parameterName: policySpec.keySet()) {
339                        JSONObject entrySpec = JSONObjectUtils.getJSONObject(policySpec, parameterName);
340                        metadataPolicy.put(MetadataPolicyEntry.parse(parameterName, entrySpec, factory, combinationValidator));
341                }
342                
343                return metadataPolicy;
344        }
345        
346        
347        /**
348         * Parses a policy for a federation entity metadata. This method is
349         * intended for policies with standard {@link PolicyOperation}s only.
350         * Uses the default {@link DefaultPolicyOperationFactory policy
351         * operation} and {@link DefaultPolicyOperationCombinationValidator
352         * policy combination validator} factories.
353         *
354         * @param policySpec The JSON object string for the policy
355         *                   specification. Must not be {@code null}.
356         *
357         * @return The metadata policy.
358         *
359         * @throws ParseException           On JSON parsing exception.
360         * @throws PolicyViolationException On a policy violation.
361         */
362        public static MetadataPolicy parse(final String policySpec)
363                throws ParseException, PolicyViolationException {
364                
365                return parse(policySpec,
366                        MetadataPolicyEntry.DEFAULT_POLICY_OPERATION_FACTORY,
367                        MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
368        }
369        
370        
371        /**
372         * Parses a policy for a federation entity metadata. This method is
373         * intended for policies including non-standard
374         * {@link PolicyOperation}s.
375         *
376         * @param policySpec           The JSON object for the policy
377         *                             specification. Must not be {@code null}.
378         * @param factory              The policy operation factory. Must not
379         *                             be {@code null}.
380         * @param combinationValidator The policy operation combination
381         *                             validator. Must not be {@code null}.
382         *
383         * @return The metadata policy.
384         *
385         * @throws ParseException           On JSON parsing exception.
386         * @throws PolicyViolationException On a policy violation.
387         */
388        public static MetadataPolicy parse(final String policySpec,
389                                           final PolicyOperationFactory factory,
390                                           final PolicyOperationCombinationValidator combinationValidator)
391                throws ParseException, PolicyViolationException {
392                
393                return parse(JSONObjectUtils.parse(policySpec), factory, combinationValidator);
394        }
395}