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.HashMap; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Map; 025 026import net.minidev.json.JSONObject; 027 028import com.nimbusds.oauth2.sdk.ParseException; 029import com.nimbusds.oauth2.sdk.util.CollectionUtils; 030import com.nimbusds.oauth2.sdk.util.StringUtils; 031import com.nimbusds.openid.connect.sdk.federation.policy.language.OperationName; 032import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyOperation; 033import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyOperationApplication; 034import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException; 035import com.nimbusds.openid.connect.sdk.federation.policy.operations.DefaultPolicyOperationCombinationValidator; 036import com.nimbusds.openid.connect.sdk.federation.policy.operations.DefaultPolicyOperationFactory; 037import com.nimbusds.openid.connect.sdk.federation.policy.operations.PolicyOperationCombinationValidator; 038import com.nimbusds.openid.connect.sdk.federation.policy.operations.PolicyOperationFactory; 039 040 041/** 042 * Policy entry for a metadata parameter. 043 * 044 * @see MetadataPolicy 045 * 046 * <p>Related specifications: 047 * 048 * <ul> 049 * <li>OpenID Connect Federation 1.0, section 4.1. 050 * </ul> 051 */ 052public class MetadataPolicyEntry implements Map.Entry<String, List<PolicyOperation>> { 053 054 055 /** 056 * The default policy operation factory. 057 */ 058 public final static PolicyOperationFactory DEFAULT_POLICY_OPERATION_FACTORY = new DefaultPolicyOperationFactory(); 059 060 061 /** 062 * The default policy operation combination validator. 063 */ 064 public final static PolicyOperationCombinationValidator DEFAULT_POLICY_COMBINATION_VALIDATOR = new DefaultPolicyOperationCombinationValidator(); 065 066 067 /** 068 * The parameter name. 069 */ 070 private final String parameterName; 071 072 073 /** 074 * The policy operations, empty list if none. 075 */ 076 private final List<PolicyOperation> policyOperations; 077 078 079 /** 080 * Creates a new policy entry for a metadata parameter. 081 * 082 * @param parameterName The parameter name. Must not be 083 * {@code null}. 084 * @param policyOperations The policy operations, empty list or 085 * {@code null} if none. 086 */ 087 public MetadataPolicyEntry(final String parameterName, final List<PolicyOperation> policyOperations) { 088 if (StringUtils.isBlank(parameterName)) { 089 throw new IllegalArgumentException("The parameter name must not be null or empty"); 090 } 091 this.parameterName = parameterName; 092 this.policyOperations = policyOperations; 093 } 094 095 096 /** 097 * Returns the parameter name. 098 * @see #getKey() 099 * 100 * @return The parameter name. 101 */ 102 public String getParameterName() { 103 return getKey(); 104 } 105 106 107 /** 108 * @see #getParameterName() 109 */ 110 @Override 111 public String getKey() { 112 return parameterName; 113 } 114 115 116 /** 117 * Returns the policy operations. 118 * @see #getValue() 119 * 120 * @return The policy operations, empty list if none. 121 */ 122 public List<PolicyOperation> getPolicyOperations() { 123 return getValue(); 124 } 125 126 127 /** 128 * @see #getPolicyOperations() 129 */ 130 @Override 131 public List<PolicyOperation> getValue() { 132 return policyOperations; 133 } 134 135 136 @Override 137 public List<PolicyOperation> setValue(final List<PolicyOperation> policyOperations) { 138 throw new UnsupportedOperationException(); 139 } 140 141 142 /** 143 * Returns a map of the operations for this policy entry. 144 * 145 * @return The map, empty if no operations. 146 */ 147 public Map<OperationName,PolicyOperation> getOperationsMap() { 148 149 Map<OperationName,PolicyOperation> map = new HashMap<>(); 150 151 if (getPolicyOperations() == null) { 152 return map; 153 } 154 155 for (PolicyOperation op: getPolicyOperations()) { 156 map.put(op.getOperationName(), op); 157 } 158 159 return map; 160 } 161 162 163 /** 164 * Combines this policy entry with another one for the same parameter 165 * name. Uses the {@link DefaultPolicyOperationCombinationValidator 166 * default policy combination validator}. 167 * 168 * @param other The other policy entry. Must not be {@code null}. 169 * 170 * @return The new combined policy entry. 171 * 172 * @throws PolicyViolationException If the parameter names don't match 173 * or another violation was 174 * encountered. 175 */ 176 public MetadataPolicyEntry combine(final MetadataPolicyEntry other) 177 throws PolicyViolationException { 178 179 return combine(other, DEFAULT_POLICY_COMBINATION_VALIDATOR); 180 } 181 182 183 /** 184 * Combines this policy entry with another one for the same parameter 185 * name. 186 * 187 * @param other The other policy entry. Must not be 188 * {@code null}. 189 * @param combinationValidator The policy operation combination 190 * validator. Must not be {@code null}. 191 * 192 * @return The new combined policy entry. 193 * 194 * @throws PolicyViolationException If the parameter names don't match 195 * or another violation was 196 * encountered. 197 */ 198 public MetadataPolicyEntry combine(final MetadataPolicyEntry other, 199 final PolicyOperationCombinationValidator combinationValidator) 200 throws PolicyViolationException { 201 202 if (! getParameterName().equals(other.getParameterName())) { 203 throw new PolicyViolationException("The parameter name of the other policy doesn't match: " + other.getParameterName()); 204 } 205 206 List<PolicyOperation> combinedOperations = new LinkedList<>(); 207 208 Map<OperationName,PolicyOperation> en1Map = getOperationsMap(); 209 Map<OperationName,PolicyOperation> en2Map = other.getOperationsMap(); 210 211 // Copy operations not present in either 212 for (OperationName name: en1Map.keySet()) { 213 if (! en2Map.containsKey(name)) { 214 combinedOperations.add(en1Map.get(name)); 215 } 216 } 217 for (OperationName name: en2Map.keySet()) { 218 if (! en1Map.containsKey(name)) { 219 combinedOperations.add(en2Map.get(name)); 220 } 221 } 222 223 // Merge operations present in both entries 224 for (OperationName opName: en1Map.keySet()) { 225 if (en2Map.containsKey(opName)) { 226 PolicyOperation op1 = en1Map.get(opName); 227 combinedOperations.add(op1.merge(en2Map.get(opName))); 228 } 229 } 230 231 List<PolicyOperation> validatedOperations = combinationValidator.validate(combinedOperations); 232 233 return new MetadataPolicyEntry(getParameterName(), validatedOperations); 234 } 235 236 237 /** 238 * Applies this policy entry for a metadata parameter to the specified 239 * value. 240 * 241 * @param value The parameter value, {@code null} if not specified. 242 * 243 * @return The resulting value, can be {@code null}. 244 * 245 * @throws PolicyViolationException On a policy violation. 246 */ 247 public Object apply(final Object value) 248 throws PolicyViolationException { 249 250 if (CollectionUtils.isEmpty(getValue())) { 251 // no ops 252 return value; 253 } 254 255 // Apply policy operations in list 256 Object updatedValue = value; 257 for (PolicyOperation op: getValue()) { 258 updatedValue = PolicyOperationApplication.apply(op, updatedValue); 259 } 260 return updatedValue; 261 } 262 263 264 /** 265 * Returns a JSON object representation of the policy operations for 266 * this entry. 267 * 268 * @return The JSON object keeping the ordering of the members. 269 */ 270 public JSONObject toJSONObject() { 271 272 if (CollectionUtils.isEmpty(getValue())) { 273 return null; 274 } 275 276 JSONObject jsonObject = new JSONObject(); 277 for (PolicyOperation operation: getValue()) { 278 // E.g. "subset_of": ["code", "code token", "code id_token"]} 279 Map.Entry<String,Object> en = operation.toJSONObjectEntry(); 280 jsonObject.put(en.getKey(), en.getValue()); 281 } 282 283 return jsonObject; 284 } 285 286 287 /** 288 * Parses a policy entry for a metadata parameter. This method is 289 * intended for policies with standard {@link PolicyOperation}s only. 290 * Uses the default {@link DefaultPolicyOperationFactory policy 291 * operation} and {@link DefaultPolicyOperationCombinationValidator 292 * policy combination validator} factories. 293 * 294 * @param parameterName The parameter name. Must not be {@code null}. 295 * @param entrySpec The JSON object entry specification, must not 296 * be {@code null}. 297 * 298 * @return The policy entry for the metadata parameter. 299 * 300 * @throws ParseException On JSON parsing exception. 301 * @throws PolicyViolationException On a policy violation. 302 */ 303 public static MetadataPolicyEntry parse(final String parameterName, 304 final JSONObject entrySpec) 305 throws ParseException, PolicyViolationException { 306 307 return parse(parameterName, entrySpec, DEFAULT_POLICY_OPERATION_FACTORY, DEFAULT_POLICY_COMBINATION_VALIDATOR); 308 } 309 310 311 /** 312 * Parses a policy entry for a metadata parameter. This method is 313 * intended for policies including non-standard 314 * {@link PolicyOperation}s. 315 * 316 * @param parameterName The parameter name. Must not be 317 * {@code null}. 318 * @param entrySpec The JSON object entry specification, 319 * must not be {@code null}. 320 * @param factory The policy operation factory. Must not 321 * be {@code null}. 322 * @param combinationValidator The policy operation combination 323 * validator. Must not be {@code null}. 324 * 325 * @return The policy entry for the metadata parameter. 326 * 327 * @throws ParseException On JSON parsing exception. 328 * @throws PolicyViolationException On a policy violation. 329 */ 330 public static MetadataPolicyEntry parse(final String parameterName, 331 final JSONObject entrySpec, 332 final PolicyOperationFactory factory, 333 final PolicyOperationCombinationValidator combinationValidator) 334 throws ParseException, PolicyViolationException { 335 336 if (entrySpec == null) { 337 throw new IllegalArgumentException("The entry spec must not be null"); 338 } 339 340 List<PolicyOperation> policyOperations = new LinkedList<>(); 341 342 for (String opName: entrySpec.keySet()) { 343 PolicyOperation op = factory.createForName(new OperationName(opName)); 344 if (op == null) { 345 throw new PolicyViolationException("Unsupported policy operation: " + opName); 346 } 347 op.parseConfiguration(entrySpec.get(opName)); 348 policyOperations.add(op); 349 } 350 351 List<PolicyOperation> validatedPolicyOperations = combinationValidator.validate(policyOperations); 352 353 return new MetadataPolicyEntry(parameterName, validatedPolicyOperations); 354 } 355}