Change ContextSet 'satisfy' behaviour (#2300)

This commit is contained in:
Luck 2020-05-20 14:51:03 +01:00 committed by GitHub
parent c09e4a1aa0
commit ed85ab1bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 294 additions and 69 deletions

View File

@ -0,0 +1,63 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.context;
import net.luckperms.api.query.OptionKey;
/**
* Mode for determining whether a {@link ContextSet} satisfies another.
*
* @since 5.2
* @see ContextSet#isSatisfiedBy(ContextSet, ContextSatisfyMode)
*/
public enum ContextSatisfyMode {
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if at least one of the
* key-value entries per key in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND (world=overworld OR world=nether)</code>.</p>
*/
AT_LEAST_ONE_VALUE_PER_KEY,
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if all key-value
* entries in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND world=overworld AND world=nether</code>.</p>
*/
ALL_VALUES_PER_KEY;
/**
* The {@link OptionKey} for {@link ContextSatisfyMode}.
*/
public static final OptionKey<ContextSatisfyMode> KEY = OptionKey.of("contextsatisfymode", ContextSatisfyMode.class);
}

View File

@ -197,17 +197,47 @@ public interface ContextSet extends Iterable<Context> {
}
/**
* Returns if this {@link ContextSet} is fully "satisfied" by another set.
* Returns if the {@link ContextSet} contains any of the given context pairings.
*
* <p>For a context set to "satisfy" another, it must itself contain all of
* the context pairings in the other set.</p>
*
* <p>Mathematically, this method returns true if this set is a <b>subset</b> of the other.</p>
*
* @param other the other set to check
* @return true if all entries in this set are also in the other set
* @param key the key to look for
* @param values the values to look for
* @return true if the set contains any of the pairs
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other);
default boolean containsAny(@NonNull String key, @NonNull Iterable<String> values) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(values, "values");
for (String value : values) {
if (contains(key, value)) {
return true;
}
}
return false;
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is the mode used by this method.</p>
*
* @param other the other set
* @return true if this context set is satisfied by the other
*/
default boolean isSatisfiedBy(@NonNull ContextSet other) {
return isSatisfiedBy(other, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set, according to the given
* {@code mode}.
*
* @param other the other set
* @param mode the mode to use
* @return true if this context set is satisfied by the other
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode);
/**
* Returns if the {@link ContextSet} is empty.

View File

@ -26,6 +26,7 @@
package net.luckperms.api.query;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
@ -166,10 +167,29 @@ public interface QueryOptions {
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @return the result
*/
boolean satisfies(@NonNull ContextSet contextSet);
default boolean satisfies(@NonNull ContextSet contextSet) {
return satisfies(contextSet, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>The {@code defaultContextSatisfyMode} parameter is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @param defaultContextSatisfyMode the default context satisfy mode to use
* @return the result
* @since 5.2
*/
boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode);
/**
* Converts this {@link QueryOptions} to a mutable builder.

View File

@ -46,11 +46,11 @@ import me.lucko.luckperms.common.util.Uuids;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import net.milkbowl.vault.permission.Permission;
@ -251,10 +251,9 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
Objects.requireNonNull(uuid, "uuid");
PermissionHolder user = lookupUser(uuid);
ContextSet contexts = getQueryOptions(uuid, world).context();
QueryOptions queryOptions = getQueryOptions(uuid, world);
return user.normalData().immutableInheritance().values().stream()
.filter(n -> n.getContexts().isSatisfiedBy(contexts))
return user.getOwnNodes(NodeType.INHERITANCE, queryOptions).stream()
.map(n -> {
Group group = this.plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (group != null) {

View File

@ -400,6 +400,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #

View File

@ -408,6 +408,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #

View File

@ -41,6 +41,7 @@ import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.model.data.TemporaryNodeMergeStrategy;
@ -114,6 +115,17 @@ public final class ConfigKeys {
return new QueryOptionsBuilderImpl(QueryMode.CONTEXTUAL).flags(flags).build();
});
/**
* The default contexts satisfy mode
*/
public static final ConfigKey<ContextSatisfyMode> CONTEXT_SATISFY_MODE = customKey(c -> {
String value = c.getString("context-satisfy-mode", "at-least-one-value-per-key");
if (value.toLowerCase().equals("all-values-per-key")) {
return ContextSatisfyMode.ALL_VALUES_PER_KEY;
}
return ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY;
});
/**
* # If the servers own UUID cache/lookup facility should be used when there is no record for a player in the LuckPerms cache.
*/

View File

@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
@ -60,6 +61,36 @@ public abstract class AbstractContextSet implements ContextSet {
return backing().containsEntry(sanitizeKey(key), sanitizeValue(value));
}
@Override
public boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode) {
if (this == other) {
return true;
}
Objects.requireNonNull(other, "other");
Objects.requireNonNull(mode, "mode");
// this is empty, it is always satisfied.
if (this.isEmpty()) {
return true;
}
// if this set isn't empty, but the other one is, then it can't be satisfied by it.
if (other.isEmpty()) {
return false;
}
// if mode is ALL_VALUES & this set has more entries than the other one, then it can't be satisfied by it.
if (mode == ContextSatisfyMode.ALL_VALUES_PER_KEY && this.size() > other.size()) {
return false;
}
// return true if 'other' contains all of 'this', according to the mode.
return otherContainsAll(other, mode);
}
protected abstract boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode);
@Override
public boolean isEmpty() {
return backing().isEmpty();

View File

@ -34,12 +34,14 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.context.MutableContextSet;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
@ -133,30 +135,31 @@ public final class ImmutableContextSetImpl extends AbstractContextSet implements
}
@Override
public boolean isSatisfiedBy(@NonNull ContextSet other) {
if (this == other) {
return true;
}
Objects.requireNonNull(other, "other");
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
return true;
} else if (other.isEmpty()) {
// this set isn't empty, but the other one is
return false;
} else if (this.size() > other.size()) {
// this set has more unique entries than the other set, so there's no way this can be satisfied.
return false;
} else {
// neither are empty, we need to compare the individual entries
Set<Map.Entry<String, String>> entries = this.map.entries();
for (Map.Entry<String, String> e : entries) {
if (!other.contains(e.getKey(), e.getValue())) {
return false;
protected boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode) {
switch (mode) {
// Use other.contains
case ALL_VALUES_PER_KEY: {
Set<Map.Entry<String, String>> entries = this.map.entries();
for (Map.Entry<String, String> e : entries) {
if (!other.contains(e.getKey(), e.getValue())) {
return false;
}
}
return true;
}
return true;
// Use other.containsAny
case AT_LEAST_ONE_VALUE_PER_KEY: {
Set<Map.Entry<String, Collection<String>>> entries = this.map.asMap().entrySet();
for (Map.Entry<String, Collection<String>> e : entries) {
if (!other.containsAny(e.getKey(), e.getValue())) {
return false;
}
}
return true;
}
default:
throw new IllegalArgumentException("Unknown mode: " + mode);
}
}

View File

@ -35,6 +35,7 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.context.MutableContextSet;
@ -186,32 +187,35 @@ public final class MutableContextSetImpl extends AbstractContextSet implements M
}
@Override
public boolean isSatisfiedBy(@NonNull ContextSet other) {
if (this == other) {
return true;
}
Objects.requireNonNull(other, "other");
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
return true;
} else if (other.isEmpty()) {
// this set isn't empty, but the other one is
return false;
} else if (this.size() > other.size()) {
// this set has more unique entries than the other set, so there's no way this can be satisfied.
return false;
} else {
// neither are empty, we need to compare the individual entries
Set<Map.Entry<String, String>> entries = this.map.entries();
synchronized (this.map) {
for (Map.Entry<String, String> e : entries) {
if (!other.contains(e.getKey(), e.getValue())) {
return false;
protected boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode) {
switch (mode) {
// Use other.contains
case ALL_VALUES_PER_KEY: {
Set<Map.Entry<String, String>> entries = this.map.entries();
synchronized (this.map) {
for (Map.Entry<String, String> e : entries) {
if (!other.contains(e.getKey(), e.getValue())) {
return false;
}
}
}
return true;
}
return true;
// Use other.containsAny
case AT_LEAST_ONE_VALUE_PER_KEY: {
Set<Map.Entry<String, Collection<String>>> entries = this.map.asMap().entrySet();
synchronized (this.map) {
for (Map.Entry<String, Collection<String>> e : entries) {
if (!other.containsAny(e.getKey(), e.getValue())) {
return false;
}
}
}
return true;
}
default:
throw new IllegalArgumentException("Unknown mode: " + mode);
}
}

View File

@ -31,11 +31,13 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import me.lucko.luckperms.common.cache.Cache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ContextSetComparator;
import me.lucko.luckperms.common.node.comparator.NodeComparator;
import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
@ -154,9 +156,13 @@ public final class NodeMap {
!flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet);
}
private ContextSatisfyMode defaultSatisfyMode() {
return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE);
}
public void forEach(QueryOptions filter, Consumer<? super Node> consumer) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
if (!filter.satisfies(e.getKey())) {
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
continue;
}
@ -176,7 +182,7 @@ public final class NodeMap {
public void copyTo(Collection<? super Node> collection, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
if (!filter.satisfies(e.getKey())) {
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
continue;
}
@ -196,7 +202,7 @@ public final class NodeMap {
public <T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
if (!filter.satisfies(e.getKey())) {
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
continue;
}
@ -224,7 +230,7 @@ public final class NodeMap {
public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> e : this.inheritanceMap.entrySet()) {
if (!filter.satisfies(e.getKey())) {
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
continue;
}

View File

@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.Flag;
@ -49,20 +50,25 @@ public class QueryOptionsImpl implements QueryOptions {
public static final QueryOptions DEFAULT_CONTEXTUAL = new QueryOptionsImpl(QueryMode.CONTEXTUAL, ImmutableContextSetImpl.EMPTY, FlagUtils.DEFAULT_FLAGS, null);
public static final QueryOptions DEFAULT_NON_CONTEXTUAL = new QueryOptionsImpl(QueryMode.NON_CONTEXTUAL, null, FlagUtils.DEFAULT_FLAGS, null);
// state
private final QueryMode mode;
private final ImmutableContextSet context;
private final byte flags;
private final ImmutableMap<OptionKey<?>, Object> options;
private final int hashCode;
// computed based on state above
private final int hashCode;
private Set<Flag> flagsSet = null;
private final ContextSatisfyMode contextSatisfyMode;
QueryOptionsImpl(QueryMode mode, @Nullable ImmutableContextSet context, byte flags, @Nullable Map<OptionKey<?>, Object> options) {
this.mode = mode;
this.context = context;
this.flags = flags;
this.options = options == null ? null : ImmutableMap.copyOf(options);
this.hashCode = calculateHashCode();
this.contextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
}
@Override
@ -113,10 +119,10 @@ public class QueryOptionsImpl implements QueryOptions {
}
@Override
public boolean satisfies(@NonNull ContextSet contextSet) {
public boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode) {
switch (this.mode) {
case CONTEXTUAL:
return contextSet.isSatisfiedBy(this.context);
return contextSet.isSatisfiedBy(this.context, this.contextSatisfyMode == null ? defaultContextSatisfyMode : this.contextSatisfyMode);
case NON_CONTEXTUAL:
return true;
default:

View File

@ -395,6 +395,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #

View File

@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ContextSetComparator;
import me.lucko.luckperms.sponge.service.ProxyFactory;
import me.lucko.luckperms.sponge.service.model.LPPermissionService;
@ -36,6 +37,7 @@ import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectData;
import me.lucko.luckperms.sponge.service.model.LPSubjectReference;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.query.QueryOptions;
@ -87,6 +89,10 @@ public class CalculatedSubjectData implements LPSubjectData {
return this.type;
}
private ContextSatisfyMode defaultSatisfyMode() {
return this.service.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE);
}
public void replacePermissions(Map<ImmutableContextSet, Map<String, Boolean>> map) {
this.permissions.clear();
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> e : map.entrySet()) {
@ -131,7 +137,7 @@ public class CalculatedSubjectData implements LPSubjectData {
// get relevant entries
SortedMap<ImmutableContextSet, Map<String, Boolean>> sorted = new TreeMap<>(ContextSetComparator.reverse());
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> entry : this.permissions.entrySet()) {
if (!filter.satisfies(entry.getKey())) {
if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) {
continue;
}
@ -209,7 +215,7 @@ public class CalculatedSubjectData implements LPSubjectData {
// get relevant entries
SortedMap<ImmutableContextSet, Set<LPSubjectReference>> sorted = new TreeMap<>(ContextSetComparator.reverse());
for (Map.Entry<ImmutableContextSet, Set<LPSubjectReference>> entry : this.parents.entrySet()) {
if (!filter.satisfies(entry.getKey())) {
if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) {
continue;
}
@ -285,7 +291,7 @@ public class CalculatedSubjectData implements LPSubjectData {
// get relevant entries
SortedMap<ImmutableContextSet, Map<String, String>> sorted = new TreeMap<>(ContextSetComparator.reverse());
for (Map.Entry<ImmutableContextSet, Map<String, String>> entry : this.options.entrySet()) {
if (!filter.satisfies(entry.getKey())) {
if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) {
continue;
}

View File

@ -409,6 +409,15 @@ inheritance-traversal-algorithm = "depth-first-pre-order"
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort = false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode = "at-least-one-value-per-key"
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #

View File

@ -399,6 +399,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #