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.data.Change; import ca.stellardrift.permissionsex.data.ImmutableSubjectData; import ca.stellardrift.permissionsex.subject.SubjectType; 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.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 com.griefdefender.provider.PermissionProvider; import com.griefdefender.registry.ClaimTypeRegistryModule; import com.griefdefender.registry.FlagRegistryModule; import kotlin.Unit; import kotlin.jvm.functions.Function1; 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.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; public class PermissionsExProvider implements PermissionProvider { private static final ContextDefinition CTX_CLAIM = new ClaimContextDefinition(); private static final ContextDefinition CTX_CLAIM_DEFAULT = new CatalogTypeContextDefinition<>(ContextKeys.CLAIM_DEFAULT, ClaimTypeRegistryModule.getInstance(), claimAttributeValue(GDClaim::getType)); private static final ContextDefinition CTX_CLAIM_OVERRIDE = new CatalogTypeContextDefinition<>(ContextKeys.CLAIM_OVERRIDE, ClaimTypeRegistryModule.getInstance()); private static final ContextDefinition CTX_FLAG = new CatalogTypeContextDefinition<>(ContextKeys.FLAG, FlagRegistryModule.getInstance()); private static final ContextDefinition CTX_SOURCE = new MultiCatalogTypeContextDefinition(ContextKeys.SOURCE, BlockTypeRegistryModule.getInstance(), ItemTypeRegistryModule.getInstance(), EntityTypeRegistryModule.getInstance()); private static final ContextDefinition CTX_STATE = new MultiCatalogTypeContextDefinition(ContextKeys.STATE); private static final ContextDefinition 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 BiConsumer> claimAttributeValue(Function claimFunc) { return (subj, collector) -> { GDClaim claim = getClaimForSubject(subj); if (claim != null) { T attr = claimFunc.apply(claim); if (attr != null) { collector.invoke(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(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.getKey(), pexCtx.getRawValue()); } private Set> contextsGDToPEX(Set gdCtxs) { return gdCtxs.stream() .map(this::contextGDToPEX) .collect(Collectors.toSet()); } private Set contextsPEXToGD(Set> 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 Map, ValueType> tKeys(Map>, ValueType> pexMap) { return tKeys(pexMap, Function.identity()); } private Map, ValueType> tKeys(Map>, InputValueType> pexMap, Function valueXform) { ImmutableMap.Builder, 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 convertResult(CompletableFuture> 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 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 getAllLoadedPlayerNames() { return getAllLoadedSubjectNames(PermissionsEx.SUBJECTS_USER); } @Override public List getAllLoadedGroupNames() { return getAllLoadedSubjectNames(PermissionsEx.SUBJECTS_GROUP); } private List getAllLoadedSubjectNames(String subjectType) { return pex.getSubjects(subjectType).getActiveSubjects().stream() .map(subj -> subj.getIdentifier().getValue()) .collect(Collectors.toList()); } @Override public void addActiveContexts(Set contexts, GDPermissionHolder permissionHolder) { addActiveContexts(contexts, permissionHolder, null, null); } @Override public void addActiveContexts(Set 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> ctxSet : in.getActiveContexts()) { if (ctxSet.contains(ctx)) { in = in.clearPermissions(ctxSet).clearOptions(ctxSet).clearParents(ctxSet); } } return in; } @Override public void clearPermissions(GDClaim claim) { ContextValue claimContext = CTX_CLAIM.createValue(claim.getUniqueId()); pex.performBulkOperation(() -> { List> 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 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 getPermissions(GDPermissionHolder holder, Set contexts) { return Maps.transformValues(holderToPEXSubject(holder).getPermissions(contextsGDToPEX(contexts)).asMap(), this::pValIntegerToBool); } @Override public Map getOptions(GDPermissionHolder holder, Set contexts) { return holderToPEXSubject(holder).getOptions(contextsGDToPEX(contexts)); } @Override public Map, Map> getPermanentPermissions(GDPermissionHolder holder) { return tKeys(holderToPEXSubject(holder).data().get().getAllPermissions(), map -> Maps.transformValues(map, this::pValIntegerToBool)); } @Override public Map, Map> getTransientPermissions(GDPermissionHolder holder) { return tKeys(holderToPEXSubject(holder).transientData().get().getAllPermissions(), map -> Maps.transformValues(map, this::pValIntegerToBool)); } @Override public Map, Map> getPermanentOptions(GDPermissionHolder holder) { return tKeys(holderToPEXSubject(holder).data().get().getAllOptions()); } @Override public Map, Map> getTransientOptions(GDPermissionHolder holder) { return tKeys(holderToPEXSubject(holder).transientData().get().getAllOptions()); } @Override public Map getPermanentOptions(GDPermissionHolder holder, Set contexts) { return holderToPEXSubject(holder).data().get().getOptions(contextsGDToPEX(contexts)); } @Override public Map getTransientOptions(GDPermissionHolder holder, Set contexts) { return holderToPEXSubject(holder).transientData().get().getOptions(contextsGDToPEX(contexts)); } @Override public Map, Map> getAllPermissions(GDPermissionHolder holder) { final Map, Map> 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 gdContexts = contextsPEXToGD(contexts); if (allPermissions.containsKey(gdContexts)) { Map 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 contexts) { return getPermissionValue(claim, holder, permission, contexts, true); } /* * The checkTransient value is ignored here -- we shouldn't need to use it since PEX already prioritizes * transient permissions appropriately based on the subject type */ @Override public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set contexts, boolean checkTransient) { return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission)); } @Override public Tristate getPermissionValue(GDPermissionHolder holder, String permission, Set contexts) { return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission)); } @Override public Tristate getPermissionValueWithRequiredContexts(GDClaim claim, GDPermissionHolder holder, String permission, Set contexts, String contextFilter) { // TODO return tristateFromInt(holderToPEXSubject(holder).getPermission(contextsGDToPEX(contexts), permission)); } @Override public String getOptionValue(GDPermissionHolder holder, Option option, Set contexts) { return holderToPEXSubject(holder).getOption(contextsGDToPEX(contexts), option.getPermission()).orElse(null); } @Override public PermissionResult setOptionValue(GDPermissionHolder holder, String permission, String value, Set contexts) { return convertResult(holderToPEXSubject(holder).data().update(data -> data.setOption(contextsGDToPEX(contexts), permission, value))).join(); } @Override public PermissionResult setPermissionValue(GDPermissionHolder holder, Flag flag, Tristate value, Set contexts) { return convertResult(holderToPEXSubject(holder).data().update(data -> data.setPermission(contextsGDToPEX(contexts), flag.getPermission(), intFromTristate(value)))).join(); } @Override public boolean setPermissionValue(GDPermissionHolder holder, String permission, Tristate value, Set contexts) { return holderToPEXSubject(holder).data().update(data -> data.setPermission(contextsGDToPEX(contexts), permission, intFromTristate(value))) .thenApply(chg -> !chg.getNew().equals(chg.getOld())).join(); } @Override public void setTransientOption(GDPermissionHolder holder, String permission, String value, Set contexts) { holderToPEXSubject(holder).transientData().update(data -> data.setOption(contextsGDToPEX(contexts), permission, value)); } @Override public void setTransientPermission(GDPermissionHolder holder, String permission, Boolean value, Set contexts) { holderToPEXSubject(holder).transientData().update(data -> data.setPermission(contextsGDToPEX(contexts), permission, pValFromBool(value))); } @Override public void refreshCachedData(GDPermissionHolder holder) { holderToPEXSubject(holder).accept(null); } }