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.operations; 019 020 021import java.util.LinkedList; 022import java.util.List; 023 024import com.nimbusds.oauth2.sdk.util.CollectionUtils; 025import com.nimbusds.openid.connect.sdk.federation.policy.language.OperationName; 026import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyOperation; 027import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException; 028 029 030/** 031 * Validates the permitted combinations of known policy operations for a given 032 * metadata parameter. 033 * 034 * <p>Supports all standard OpenID Connect federation policy operations: 035 * 036 * <ul> 037 * <li>{@link SubsetOfOperation subset_of} 038 * <li>{@link OneOfOperation one_of} 039 * <li>{@link SupersetOfOperation superset_of} 040 * <li>{@link AddOperation add} 041 * <li>{@link ValueOperation value} 042 * <li>{@link DefaultOperation default} 043 * <li>{@link EssentialOperation essential} 044 * </ul> 045 * 046 * <p>Override the {@link #validate(List)} method to support additional custom 047 * policies. 048 * 049 * <p>Related specifications: 050 * 051 * <ul> 052 * <li>OpenID Connect Federation 1.0, section 4.2. 053 * </ul> 054 */ 055public class DefaultPolicyOperationCombinationValidator implements PolicyOperationCombinationValidator { 056 057 058 @Override 059 public List<PolicyOperation> validate(final List<PolicyOperation> policyOperations) 060 throws PolicyViolationException { 061 062 if (CollectionUtils.isEmpty(policyOperations) || policyOperations.size() == 1) { 063 // Empty or one policy operation always pass 064 return policyOperations; 065 } 066 067 List<PolicyOperation> currentOpList = new LinkedList<>(policyOperations); 068 069 currentOpList = validateCombinationsOfEssential(currentOpList); 070 currentOpList = validateCombinationsOfAdd(currentOpList); 071 currentOpList = validateCombinationsOfDefault(currentOpList); 072 currentOpList = validateCombinationsOfSupersetOf(currentOpList); 073 currentOpList = validateCombinationsOfSubsetOf(currentOpList); 074 currentOpList = validateCombinationsOfValue(currentOpList); 075 076 return currentOpList; 077 } 078 079 080 private static List<PolicyOperation> validateCombinationsOfEssential(final List<PolicyOperation> ops) { 081 // Can be combined with all the others. If essential is not present 082 // that is the same as stating essential=true. 083 return ops; 084 } 085 086 087 private static List<PolicyOperation> validateCombinationsOfAdd(final List<PolicyOperation> ops) { 088 // No official spec about "add" 089 return ops; 090 } 091 092 093 private static List<PolicyOperation> validateCombinationsOfDefault(final List<PolicyOperation> ops) 094 throws PolicyViolationException { 095 // Can be combined with one_of, subset_of and superset_of. If a 096 // default policy is combined with one_of, subset_of or superset_of 097 // and it is not a subset of the subset_of policy or the one_of 098 // policy or a superset of the superset_of policy then an error MUST 099 // be raised. 100 DefaultOperation o = Utils.getPolicyOperationByType(ops, DefaultOperation.class); 101 102 if (o == null) { 103 return ops; 104 } 105 106 if (o.getStringListConfiguration() != null) { 107 ensureSatisfiedBySubsetOf(ops, o.getStringListConfiguration()); 108 ensureSatisfiedBySupersetOf(ops, o.getStringListConfiguration()); 109 } else if (o.getStringConfiguration() != null) { 110 ensureSatisfiedByOneOf(ops, o.getStringConfiguration()); 111 } 112 113 if (Utils.getPolicyOperationByType(ops, ValueOperation.class) != null) { 114 throw new PolicyViolationException("Policies default and value cannot be combined"); 115 } 116 117 return ops; 118 } 119 120 121 private static List<PolicyOperation> validateCombinationsOfSupersetOf(final List<PolicyOperation> ops) 122 throws PolicyViolationException { 123 // Can be combined with subset_of. If subset_of and superset_of both 124 // appears in a metadata_policy statement subset_of MUST be a superset 125 // of superset_of. 126 SupersetOfOperation o = Utils.getPolicyOperationByType(ops, SupersetOfOperation.class); 127 if (o == null) { 128 return ops; 129 } 130 131 SubsetOfOperation subsetOfOperation = Utils.getPolicyOperationByType(ops, SubsetOfOperation.class); 132 if (subsetOfOperation != null) { 133 ensureSatisfied(o, subsetOfOperation.getStringListConfiguration()); 134 } 135 136 return ops; 137 } 138 139 140 private static List<PolicyOperation> validateCombinationsOfSubsetOf(final List<PolicyOperation> ops) 141 throws PolicyViolationException { 142 // Can be combined with superset_of. If superset_of and subset_of both 143 // appears in a metadata_policy statement for a claim subset_of MUST be 144 // a superset of superset_of. 145 SubsetOfOperation o = Utils.getPolicyOperationByType(ops, SubsetOfOperation.class); 146 if (o == null) { 147 return ops; 148 } 149 150 SupersetOfOperation supersetOfOperation = Utils.getPolicyOperationByType(ops, SupersetOfOperation.class); 151 if (supersetOfOperation != null) { 152 ensureSatisfied(supersetOfOperation, o.getStringListConfiguration()); 153 } 154 155 return ops; 156 } 157 158 159 // See https://bitbucket.org/openid/connect/issues/1163/federation-metadata-policy-current-spec 160 private static List<PolicyOperation> validateCombinationsOfValue(final List<PolicyOperation> ops) 161 throws PolicyViolationException { 162 // https://bitbucket.org/openid/connect/issues/1163/federation-metadata-policy-current-spec 163 // value must not be combined with other policy types, raise an error if this condition is detected. 164 ValueOperation o = Utils.getPolicyOperationByType(ops, ValueOperation.class); 165 if (o == null) { 166 return ops; 167 } 168 169 List<OperationName> violating = new LinkedList<>(); 170 for (PolicyOperation op: ops) { 171 if (op instanceof ValueOperation) { 172 // ok 173 } else if (op instanceof EssentialOperation) { 174 // ok 175 } else { 176 violating.add(op.getOperationName()); 177 } 178 } 179 180 if (! violating.isEmpty()) { 181 throw new PolicyViolationException("Policy operation " + ValueOperation.NAME + " must not be combined with: " + violating); 182 } 183 184 return ops; 185 } 186 187 188 private static void ensureSatisfied(final SubsetOfOperation op, final List<String> values) 189 throws PolicyViolationException { 190 if (! op.getStringListConfiguration().containsAll(values)) 191 throw new PolicyViolationException("Not in " + SubsetOfOperation.NAME + " " + op.getStringListConfiguration() + ": " + values); 192 } 193 194 195 private static void ensureSatisfiedBySubsetOf(final List<PolicyOperation> policyOperations, final List<String> values) 196 throws PolicyViolationException { 197 SubsetOfOperation op = Utils.getPolicyOperationByType(policyOperations, SubsetOfOperation.class); 198 if (op != null) { 199 ensureSatisfied(op, values); 200 } 201 } 202 203 204 private static void ensureSatisfied(final SupersetOfOperation op, final List<String> values) 205 throws PolicyViolationException { 206 if (! values.containsAll(op.getStringListConfiguration())) 207 throw new PolicyViolationException("Not in " + SupersetOfOperation.NAME + " " + op.getStringListConfiguration() + ": " + values); 208 } 209 210 211 private static void ensureSatisfiedBySupersetOf(final List<PolicyOperation> policyOperations, final List<String> values) 212 throws PolicyViolationException { 213 SupersetOfOperation op = Utils.getPolicyOperationByType(policyOperations, SupersetOfOperation.class); 214 if (op != null) { 215 ensureSatisfied(op, values); 216 } 217 } 218 219 220 private static void ensureSatisfiedByOneOf(final List<PolicyOperation> policyOperations, final String value) 221 throws PolicyViolationException { 222 OneOfOperation op = Utils.getPolicyOperationByType(policyOperations, OneOfOperation.class); 223 if (op == null) return; 224 if (! op.getStringListConfiguration().contains(value)) 225 throw new PolicyViolationException("Not in " + OneOfOperation.NAME + " " + op.getStringListConfiguration() + ": " + value); 226 } 227}