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