472 lines
20 KiB
Java
472 lines
20 KiB
Java
/*
|
|
* This file is part of GriefDefender, licensed under the MIT License (MIT).
|
|
*
|
|
* Copyright (c) bloodmc
|
|
* Copyright (c) zml
|
|
* 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 com.griefdefender.provider.permissionsex;
|
|
|
|
import ca.stellardrift.permissionsex.bukkit.PermissionsExPlugin;
|
|
import ca.stellardrift.permissionsex.context.ContextDefinition;
|
|
import ca.stellardrift.permissionsex.context.ContextValue;
|
|
import ca.stellardrift.permissionsex.subject.SubjectType;
|
|
import ca.stellardrift.permissionsex.util.Change;
|
|
import ca.stellardrift.permissionsex.util.Util;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Maps;
|
|
import com.griefdefender.GDPlayerData;
|
|
import com.griefdefender.GriefDefenderPlugin;
|
|
import com.griefdefender.api.CatalogType;
|
|
import com.griefdefender.api.Tristate;
|
|
import com.griefdefender.api.claim.Claim;
|
|
import com.griefdefender.api.claim.ClaimType;
|
|
import com.griefdefender.api.permission.Context;
|
|
import com.griefdefender.api.permission.ContextKeys;
|
|
import com.griefdefender.api.permission.PermissionResult;
|
|
import com.griefdefender.api.permission.ResultTypes;
|
|
import com.griefdefender.api.permission.flag.Flag;
|
|
import com.griefdefender.api.permission.flag.FlagDefinition;
|
|
import com.griefdefender.api.permission.option.Option;
|
|
import com.griefdefender.claim.GDClaim;
|
|
import com.griefdefender.internal.registry.BlockTypeRegistryModule;
|
|
import com.griefdefender.internal.registry.EntityTypeRegistryModule;
|
|
import com.griefdefender.internal.registry.ItemTypeRegistryModule;
|
|
import com.griefdefender.permission.GDPermissionHolder;
|
|
import com.griefdefender.permission.GDPermissionResult;
|
|
import com.griefdefender.permission.GDPermissionUser;
|
|
import ca.stellardrift.permissionsex.PermissionsEx;
|
|
import ca.stellardrift.permissionsex.subject.CalculatedSubject;
|
|
import ca.stellardrift.permissionsex.subject.ImmutableSubjectData;
|
|
|
|
import com.griefdefender.provider.PermissionProvider;
|
|
import com.griefdefender.registry.ClaimTypeRegistryModule;
|
|
import com.griefdefender.registry.FlagRegistryModule;
|
|
import net.kyori.text.Component;
|
|
import net.kyori.text.TextComponent;
|
|
import net.kyori.text.event.HoverEvent;
|
|
import net.kyori.text.format.TextColor;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.plugin.Plugin;
|
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.stream.Collectors;
|
|
|
|
public class PermissionsExProvider implements PermissionProvider {
|
|
private static final ContextDefinition<UUID> CTX_CLAIM = new ClaimContextDefinition();
|
|
private static final ContextDefinition<ClaimType> CTX_CLAIM_DEFAULT = new CatalogTypeContextDefinition<>(ContextKeys.CLAIM_DEFAULT, ClaimTypeRegistryModule.getInstance(), claimAttributeValue(GDClaim::getType));
|
|
private static final ContextDefinition<ClaimType> CTX_CLAIM_OVERRIDE = new CatalogTypeContextDefinition<>(ContextKeys.CLAIM_OVERRIDE, ClaimTypeRegistryModule.getInstance());
|
|
private static final ContextDefinition<Flag> CTX_FLAG = new CatalogTypeContextDefinition<>(ContextKeys.FLAG, FlagRegistryModule.getInstance());
|
|
private static final ContextDefinition<CatalogType> CTX_SOURCE = new MultiCatalogTypeContextDefinition(ContextKeys.SOURCE, BlockTypeRegistryModule.getInstance(), ItemTypeRegistryModule.getInstance(), EntityTypeRegistryModule.getInstance());
|
|
private static final ContextDefinition<CatalogType> CTX_STATE = new MultiCatalogTypeContextDefinition(ContextKeys.STATE);
|
|
private static final ContextDefinition<CatalogType> CTX_TARGET = new MultiCatalogTypeContextDefinition(ContextKeys.TARGET);
|
|
|
|
private final PermissionsEx<?> pex;
|
|
|
|
public PermissionsExProvider(PermissionsEx<?> engine) {
|
|
this.pex = engine;
|
|
engine.registerContextDefinition(CTX_CLAIM);
|
|
engine.registerContextDefinition(CTX_CLAIM_DEFAULT);
|
|
engine.registerContextDefinition(CTX_CLAIM_OVERRIDE);
|
|
engine.registerContextDefinition(CTX_FLAG);
|
|
engine.registerContextDefinition(CTX_SOURCE);
|
|
engine.registerContextDefinition(CTX_STATE);
|
|
engine.registerContextDefinition(CTX_TARGET);
|
|
}
|
|
|
|
public static PermissionsExProvider initBukkit(Plugin pexPlugin) {
|
|
if (pexPlugin instanceof PermissionsExPlugin) {
|
|
return new PermissionsExProvider(((PermissionsExPlugin) pexPlugin).getManager());
|
|
}
|
|
throw new RuntimeException("Provided plugin " + pexPlugin + " was not a proper instance of PermissionsExPlugin");
|
|
}
|
|
|
|
private static <T> BiConsumer<CalculatedSubject, Consumer<? super T>> claimAttributeValue(Function<GDClaim, T> claimFunc) {
|
|
return (subj, collector) -> {
|
|
GDClaim claim = getClaimForSubject(subj);
|
|
if (claim != null) {
|
|
T attr = claimFunc.apply(claim);
|
|
if (attr != null) {
|
|
collector.accept(attr);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the current applicable claim for a given subject for permissions purposes. This takes into account a claim being ignored as well
|
|
*
|
|
* @param subj The subject to get the active claim for
|
|
* @return A claim if applicable, otherwise null
|
|
*/
|
|
static GDClaim getClaimForSubject(CalculatedSubject subj) {
|
|
Player ply = Util.castOptional(Optional.ofNullable(subj.getAssociatedObject()), Player.class).orElse(null);
|
|
if (ply == null) {// not an online player
|
|
return null;
|
|
}
|
|
GDPlayerData plyData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(ply.getWorld(), ply.getUniqueId());
|
|
|
|
if (plyData != null && plyData.ignoreActiveContexts) {
|
|
plyData.ignoreActiveContexts = false;
|
|
return null;
|
|
}
|
|
|
|
GDClaim ret = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(plyData, ply.getLocation());
|
|
if (plyData != null && !plyData.canIgnoreClaim(ret)) {
|
|
return null;
|
|
}
|
|
|
|
GDClaim parentClaim = ret.parent;
|
|
if (ret.getData().doesInheritParent() && parentClaim != null) {
|
|
return parentClaim;
|
|
} else {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// - Data conversion
|
|
|
|
private ContextValue<?> contextGDToPEX(Context gdCtx) {
|
|
return new ContextValue<>(gdCtx.getKey(), gdCtx.getValue());
|
|
}
|
|
|
|
private Context contextPEXToGD(ContextValue<?> pexCtx) {
|
|
return new Context(pexCtx.key(), pexCtx.rawValue());
|
|
}
|
|
|
|
private Set<ContextValue<?>> contextsGDToPEX(Set<Context> gdCtxs) {
|
|
return gdCtxs.stream()
|
|
.map(this::contextGDToPEX)
|
|
.collect(Collectors.toSet());
|
|
|
|
}
|
|
|
|
private Set<Context> contextsPEXToGD(Set<ContextValue<?>> ctxs) {
|
|
return ctxs.stream()
|
|
.map(this::contextPEXToGD)
|
|
.collect(Collectors.toSet());
|
|
}
|
|
|
|
private CalculatedSubject holderToPEXSubject(GDPermissionHolder holder) {
|
|
return pex.getSubjects(holder instanceof GDPermissionUser ? PermissionsEx.SUBJECTS_USER : PermissionsEx.SUBJECTS_GROUP).get(holder.getIdentifier()).join();
|
|
}
|
|
|
|
private <ValueType> Map<Set<Context>, ValueType> tKeys(Map<Set<ContextValue<?>>, ValueType> pexMap) {
|
|
return tKeys(pexMap, Function.identity());
|
|
}
|
|
|
|
private <ValueType, InputValueType> Map<Set<Context>, ValueType> tKeys(Map<Set<ContextValue<?>>, InputValueType> pexMap, Function<InputValueType, ValueType> valueXform) {
|
|
ImmutableMap.Builder<Set<Context>, ValueType> ret = ImmutableMap.builder();
|
|
pexMap.forEach((key, val) -> ret.put(contextsPEXToGD(key), valueXform.apply(val)));
|
|
return ret.build();
|
|
}
|
|
|
|
private Component textForError(Throwable t) { // TODO: is this already done somewhere else in GD?
|
|
TextComponent.Builder build = TextComponent.builder(t.getMessage(), TextColor.RED);
|
|
|
|
TextComponent.Builder stackTrace = TextComponent.builder();
|
|
for (StackTraceElement el : t.getStackTrace()) {
|
|
stackTrace.append(el.toString()).append("\n");
|
|
}
|
|
|
|
build.hoverEvent(HoverEvent.showText(stackTrace.build()));
|
|
return build.build();
|
|
}
|
|
|
|
private CompletableFuture<PermissionResult> convertResult(CompletableFuture<Change<ImmutableSubjectData>> pexResult) {
|
|
return pexResult.handle((res, err) -> {
|
|
if (err != null) {
|
|
return new GDPermissionResult(ResultTypes.FAILURE, textForError(err));
|
|
} else {
|
|
return new GDPermissionResult(ResultTypes.SUCCESS);
|
|
}
|
|
});
|
|
}
|
|
|
|
private Tristate tristateFromInt(int value) {
|
|
if (value > 0) {
|
|
return Tristate.TRUE;
|
|
} else if (value < 0) {
|
|
return Tristate.FALSE;
|
|
}
|
|
return Tristate.UNDEFINED;
|
|
}
|
|
|
|
private int intFromTristate(Tristate value) {
|
|
switch (value) {
|
|
case TRUE: return 1;
|
|
case FALSE: return -1;
|
|
case UNDEFINED: return 0;
|
|
default: throw new IllegalArgumentException("unknown tristate value " + value);
|
|
}
|
|
}
|
|
|
|
private int pValFromBool(@Nullable Boolean value) {
|
|
if (value == null) {
|
|
return 0;
|
|
}
|
|
return value ? 1 : -1;
|
|
}
|
|
|
|
private boolean pValIntegerToBool(@Nullable Integer value) {
|
|
return value != null && value > 0;
|
|
}
|
|
|
|
// - Implement API
|
|
|
|
@Override
|
|
public boolean createDefaultGroup(String identifier) {
|
|
if (!this.hasGroupSubject(identifier)) {
|
|
pex.createSubjectIdentifier(PermissionsEx.SUBJECTS_GROUP, identifier);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public String getServerName() {
|
|
return pex.getConfig().getServerTags().get(0);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasGroupSubject(String identifier) {
|
|
return pex.getSubjects(PermissionsEx.SUBJECTS_GROUP).isRegistered(identifier).join();
|
|
}
|
|
|
|
@Override
|
|
public UUID lookupUserUniqueId(String name) {
|
|
return Bukkit.getOfflinePlayer(name).getUniqueId(); // TODO: this is not a thing pex does, should be a platform thing
|
|
}
|
|
|
|
@Override
|
|
public List<String> getAllLoadedPlayerNames() {
|
|
return getAllLoadedSubjectNames(PermissionsEx.SUBJECTS_USER);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getAllLoadedGroupNames() {
|
|
return getAllLoadedSubjectNames(PermissionsEx.SUBJECTS_GROUP);
|
|
}
|
|
|
|
private List<String> getAllLoadedSubjectNames(String subjectType) {
|
|
return pex.getSubjects(subjectType).getActiveSubjects().stream()
|
|
.map(subj -> subj.getIdentifier().getValue())
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
@Override
|
|
public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder) {
|
|
addActiveContexts(contexts, permissionHolder, null, null);
|
|
}
|
|
|
|
@Override
|
|
public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder, GDPlayerData playerData, Claim claim) {
|
|
contexts.addAll(contextsPEXToGD(holderToPEXSubject(permissionHolder).getActiveContexts()));
|
|
// ??? TODO anything else
|
|
}
|
|
|
|
private ImmutableSubjectData clearContext(ImmutableSubjectData in, ContextValue<?> ctx) {
|
|
for (Set<ContextValue<?>> ctxSet : in.getActiveContexts()) {
|
|
if (ctxSet.contains(ctx)) {
|
|
in = in.clearPermissions(ctxSet).clearOptions(ctxSet).clearParents(ctxSet);
|
|
}
|
|
}
|
|
return in;
|
|
|
|
}
|
|
|
|
@Override
|
|
public void clearPermissions(GDClaim claim) {
|
|
ContextValue<UUID> claimContext = CTX_CLAIM.createValue(claim.getUniqueId());
|
|
pex.performBulkOperation(() -> {
|
|
List<CompletableFuture<?>> dataAwait = new LinkedList<>();
|
|
pex.getRegisteredSubjectTypes().forEach(type -> {
|
|
SubjectType subjects = pex.getSubjects(type);
|
|
subjects.getAllIdentifiers().forEach(ident -> {
|
|
dataAwait.add(subjects.persistentData().getReference(ident).thenCombine(subjects.transientData().getReference(ident), (persist, trans) -> {
|
|
return CompletableFuture.allOf(persist.update(data -> clearContext(data, claimContext)),
|
|
trans.update(data -> clearContext(data, claimContext)));
|
|
}));
|
|
});
|
|
});
|
|
return CompletableFuture.allOf(dataAwait.toArray(new CompletableFuture[0]));
|
|
}).join();
|
|
}
|
|
|
|
@Override
|
|
public void clearPermissions(GDPermissionHolder holder, Context context) {
|
|
holderToPEXSubject(holder).data().update(data -> data.clearPermissions(ImmutableSet.of(contextGDToPEX(context))));
|
|
}
|
|
|
|
@Override
|
|
public void clearPermissions(GDPermissionHolder holder, Set<Context> contexts) {
|
|
holderToPEXSubject(holder).data().update(data -> data.clearPermissions(contextsGDToPEX(contexts)));
|
|
}
|
|
|
|
@Override
|
|
public boolean holderHasPermission(GDPermissionHolder holder, String permission) {
|
|
return holderToPEXSubject(holder).hasPermission(permission);
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Boolean> getPermissions(GDPermissionHolder holder, Set<Context> contexts) {
|
|
return Maps.transformValues(holderToPEXSubject(holder).getPermissions(contextsGDToPEX(contexts)).asMap(), this::pValIntegerToBool);
|
|
}
|
|
|
|
@Override
|
|
public Map<String, List<String>> getOptions(GDPermissionHolder holder, Set<Context> contexts) {
|
|
//return holderToPEXSubject(holder).getOptions(contextsGDToPEX(contexts));
|
|
return new HashMap<>();
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, Boolean>> getAllPermanentPermissions() {
|
|
// TODO
|
|
return new HashMap<>();
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, Boolean>> getPermanentPermissions(GDPermissionHolder holder) {
|
|
return tKeys(holderToPEXSubject(holder).data().get().getAllPermissions(), map -> Maps.transformValues(map, this::pValIntegerToBool));
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, Boolean>> getTransientPermissions(GDPermissionHolder holder) {
|
|
return tKeys(holderToPEXSubject(holder).transientData().get().getAllPermissions(), map -> Maps.transformValues(map, this::pValIntegerToBool));
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, List<String>>> getPermanentOptions(GDPermissionHolder holder) {
|
|
//return tKeys(holderToPEXSubject(holder).data().get().getAllOptions());
|
|
return new HashMap<>();
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, List<String>>> getTransientOptions(GDPermissionHolder holder) {
|
|
//return tKeys(holderToPEXSubject(holder).transientData().get().getAllOptions());
|
|
return new HashMap<>();
|
|
}
|
|
|
|
@Override
|
|
public Map<Set<Context>, Map<String, Boolean>> getAllPermissions(GDPermissionHolder holder) {
|
|
final Map<Set<Context>, Map<String, Boolean>> allPermissions = new HashMap<>();
|
|
holderToPEXSubject(holder).data().get().getAllPermissions().forEach((contexts, perms) ->
|
|
allPermissions.put(contextsPEXToGD(contexts), new HashMap<>(Maps.transformValues(perms, this::pValIntegerToBool))));
|
|
|
|
holderToPEXSubject(holder).transientData().get().getAllPermissions().forEach((contexts, perms) -> {
|
|
Set<Context> gdContexts = contextsPEXToGD(contexts);
|
|
if (allPermissions.containsKey(gdContexts)) {
|
|
Map<String, Boolean> ctxPerms = allPermissions.get(gdContexts);
|
|
perms.forEach((k, v) -> ctxPerms.put(k, v > 0));
|
|
} else {
|
|
allPermissions.put(gdContexts, Maps.transformValues(perms, this::pValIntegerToBool));
|
|
}
|
|
});
|
|
return Collections.unmodifiableMap(allPermissions);
|
|
}
|
|
|
|
@Override
|
|
public Tristate getPermissionValue(GDPermissionHolder holder, String permission) {
|
|
return tristateFromInt(holderToPEXSubject(holder).getPermission(permission));
|
|
}
|
|
|
|
@Override
|
|
public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts) {
|
|
return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission));
|
|
}
|
|
|
|
@Override
|
|
public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, PermissionDataType type) {
|
|
return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission));
|
|
}
|
|
|
|
@Override
|
|
public Tristate getPermissionValue(GDPermissionHolder holder, String permission, Set<Context> contexts) {
|
|
return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission));
|
|
}
|
|
|
|
@Override
|
|
public String getOptionValue(GDPermissionHolder holder, Option option, Set<Context> contexts) {
|
|
return holderToPEXSubject(holder).getOption(contextsGDToPEX(contexts), option.getPermission()).orElse(null);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getOptionValueList(GDPermissionHolder holder, Option option, Set<Context> contexts) {
|
|
final List<String> valueList = new ArrayList<>();
|
|
valueList.add(holderToPEXSubject(holder).getOption(contextsGDToPEX(contexts), option.getPermission()).orElse(null));
|
|
return valueList;
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<PermissionResult> setOptionValue(GDPermissionHolder holder, String permission, String value, Set<Context> contexts, boolean check) {
|
|
return convertResult(holderToPEXSubject(holder).data().update(data -> data.setOption(contextsGDToPEX(contexts), permission, value)));
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<PermissionResult> setTransientOption(GDPermissionHolder holder, String permission, String value, Set<Context> contexts) {
|
|
return convertResult(holderToPEXSubject(holder).transientData().update(data -> data.setOption(contextsGDToPEX(contexts), permission, value)));
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<PermissionResult> setTransientPermission(GDPermissionHolder holder, String permission, Tristate value, Set<Context> contexts) {
|
|
return convertResult(holderToPEXSubject(holder).transientData().update(data -> data.setPermission(contextsGDToPEX(contexts), permission, pValFromBool(value.asBoolean()))));
|
|
}
|
|
|
|
@Override
|
|
public void refreshCachedData(GDPermissionHolder holder) {
|
|
// TODO
|
|
//holderToPEXSubject(holder).data().getCache().invalidate(holder.getIdentifier());
|
|
//holderToPEXSubject(holder).transientData().getCache().invalidate(holder.getIdentifier());
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<PermissionResult> setPermissionValue(GDPermissionHolder holder, String permission, Tristate value, Set<Context> contexts, boolean check, boolean save) {
|
|
return convertResult(holderToPEXSubject(holder).data().update(data -> data.setPermission(contextsGDToPEX(contexts), permission, intFromTristate(value))));
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<Void> save(GDPermissionHolder holder) {
|
|
// TODO
|
|
return new CompletableFuture<>();
|
|
}
|
|
|
|
@Override
|
|
public CompletableFuture<PermissionResult> setFlagDefinition(GDPermissionHolder holder, FlagDefinition definition, Tristate value, Set<Context> contexts, boolean isTransient) {
|
|
// TODO
|
|
return new CompletableFuture<>();
|
|
}
|
|
}
|
|
|