/**
* PermissionsEx
* Copyright (C) zml and PermissionsEx contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ninja.leaping.permissionsex.bukkit;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import ninja.leaping.permissionsex.PermissionsEx;
import ninja.leaping.permissionsex.subject.CalculatedSubject;
import ninja.leaping.permissionsex.util.NodeTree;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.permissions.PermissionRemovedExecutor;
import org.bukkit.plugin.Plugin;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;
import static ninja.leaping.permissionsex.PermissionsEx.SUBJECTS_GROUP;
import static ninja.leaping.permissionsex.PermissionsEx.SUBJECTS_USER;
import static ninja.leaping.permissionsex.bukkit.BukkitTranslations.t;
/**
* Implementation of Permissible using PEX for data
*/
public class PEXPermissible extends PermissibleBase {
private static Metapermission[] METAPERMISSIONS = new Metapermission[] {
/**
* | Permission | Usage
|----------------------------|------
| `group.<group>` | Added for each group a user is in
| `groups.<group>` | same as above
| `options.<option>.<value>` | Each option the user has
| `prefix.<prefix>` | User's prefix
| `suffix.<suffix>` | User's suffix
*/
new Metapermission(Pattern.compile("groups?\\.(?<name>.+)")) {
@Override
public boolean isMatch(Matcher result, CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
return subj.getParents(contexts).contains(Maps.immutableEntry(SUBJECTS_GROUP, result.group("name")));
}
@Override
public Iterator<String> getValues(CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
return subj.getParents(contexts).stream()
.filter(ent -> ent.getKey().equals(SUBJECTS_GROUP))
.flatMap(ent -> StreamSupport.<String>stream(Spliterators.spliterator(new String[]{"group." + ent.getValue(), "groups." + ent.getValue()}, Spliterator.IMMUTABLE | Spliterator.DISTINCT), false))
.iterator();
}
},
new Metapermission(Pattern.compile("options\\.(?<key>.*)\\.(?<value>.*)")) {
@Override
public boolean isMatch(Matcher result, CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
return subj.getOption(contexts, result.group("key")).map(val -> val.equals(result.group("value"))).orElse(false);
}
@Override
public Iterator<String> getValues(CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
return Iterables.transform(subj.getOptions(contexts).entrySet(),
ent -> "options." + ent.getKey() + "." + ent.getValue())
.iterator();
}
},
new SpecificOptionMetapermission("prefix"),
new SpecificOptionMetapermission("suffix")
};
private abstract static class Metapermission {
/**
* Pattern to match against
*/
private final Pattern matchAgainst;
protected Metapermission(Pattern matchAgainst) {
this.matchAgainst = matchAgainst;
}
public abstract boolean isMatch(Matcher result, CalculatedSubject subj, Set<Map.Entry<String, String>> contexts);
public abstract Iterator<String> getValues(CalculatedSubject subj, Set<Map.Entry<String, String>> contexts);
}
private static class SpecificOptionMetapermission extends Metapermission {
private final String option;
public SpecificOptionMetapermission(String option) {
super(Pattern.compile(Pattern.quote(option) + "\\.(?<value>.+)"));
this.option = option;
}
@Override
public boolean isMatch(Matcher result, CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
return subj.getOption(contexts, option).map(val -> val.equals(result.group("value"))).orElse(false);
}
@Override
public Iterator<String> getValues(CalculatedSubject subj, Set<Map.Entry<String, String>> contexts) {
String ret = subj.getOptions(contexts).get(option);
return ret == null ? Iterators.emptyIterator() : Iterators.singletonIterator(this.option + "." + ret);
}
}
private final Player player;
private final PermissionsExPlugin plugin;
private PermissionsEx pex;
private CalculatedSubject subj;
private Permissible previousPermissible;
private final Set<PEXPermissionAttachment> attachments = new HashSet<>();
public PEXPermissible(Player player, PermissionsExPlugin plugin) throws ExecutionException, InterruptedException {
super(player);
this.player = player;
this.plugin = plugin;
this.pex = plugin.getManager();
this.subj = pex.getSubjects(SUBJECTS_USER).get(player.getUniqueId().toString()).get();
}
CalculatedSubject getPEXSubject() {
return this.subj;
}
public PermissionsEx getManager() {
return this.pex;
}
@Override
public boolean isOp() {
return super.isOp(); // TODO: Implement op handling
}
@Override
public void setOp(boolean value) {
super.setOp(value);
}
@Override
public boolean isPermissionSet(String name) {
Preconditions.checkNotNull(name, "name");
name = name.toLowerCase();
return getPermissionValue(getActiveContexts(), name) != 0;
}
private int getPermissionValue(Set<Map.Entry<String, String>> contexts, String permission) {
int ret = getPermissionValue0(subj.getPermissions(contexts), permission);
if (ret == 0) {
for (Metapermission mPerm : METAPERMISSIONS) {
Matcher match = mPerm.matchAgainst.matcher(permission);
if (match.matches() && mPerm.isMatch(match, subj, contexts)) {
ret = 1;
}
}
}
if (pex.hasDebugMode()) {
pex.getLogger().info(t("Checked permission %s for player %s in contexts %s: %s", permission, player.getName(), contexts, ret));
}
return ret;
}
private int getPermissionValue0(NodeTree nodeTree, String name) {
int val = nodeTree.get(name);
if (val != 0) {
return val;
}
for (Map.Entry<String, Boolean> ent : plugin.getPermissionList().getParents(name)) {
val = getPermissionValue0(nodeTree, ent.getKey());
if (!ent.getValue()) {
val = -val;
}
if (val != 0) {
return val;
}
}
return 0;
}
@Override
public boolean isPermissionSet(Permission perm) {
Preconditions.checkNotNull(perm, "perm");
return isPermissionSet(perm.getName());
}
@Override
public boolean hasPermission(String inName) {
Preconditions.checkNotNull(inName, "inName");
inName = inName.toLowerCase();
return getPermissionValue(getActiveContexts(), inName) > 0;
}
@Override
public boolean hasPermission(Permission perm) {
Preconditions.checkNotNull(perm, "perm");
return hasPermission(perm.getName());
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return super.addAttachment(plugin, name, value);
}
@Override
public PermissionAttachment addAttachment(Plugin plugin) {
final PEXPermissionAttachment attach = new PEXPermissionAttachment(plugin, player, this);
this.subj.transientData().update(input -> input.addParent(PermissionsEx.GLOBAL_CONTEXT, PEXPermissionAttachment.ATTACHMENT_TYPE, attach.getIdentifier()))
.thenRun(() -> this.attachments.add(attach));
return attach;
}
public boolean removeAttachmentInternal(final PEXPermissionAttachment attach) {
this.subj.transientData().update(input -> input.removeParent(PermissionsEx.GLOBAL_CONTEXT, PEXPermissionAttachment.ATTACHMENT_TYPE, attach.getIdentifier()))
.thenRun(() -> {
PermissionRemovedExecutor exec = attach.getRemovalCallback();
if (exec != null) {
exec.attachmentRemoved(attach);
}
});
return true;
}
@Override
public void removeAttachment(PermissionAttachment attachment) {
if (!(attachment instanceof PEXPermissionAttachment)) {
throw new IllegalArgumentException("Provided attachment was not a PEX attachment!");
}
removeAttachmentInternal(((PEXPermissionAttachment) attachment));
this.attachments.remove(attachment);
}
void removeAllAttachments() {
for (PEXPermissionAttachment attach : this.attachments) {
removeAttachmentInternal(attach);
}
this.attachments.clear();
}
@Override
public void recalculatePermissions() { // We don't need this currently? Guess could clear cache somehow, but automated should get it right. well, except for people adding children to permissions -- that is just weird
}
@Override
public synchronized void clearPermissions() {
// todo
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return super.addAttachment(plugin, name, value, ticks);
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return addAttachment(plugin); // TODO: Implement timed permissions
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
ImmutableSet.Builder<PermissionAttachmentInfo> ret = ImmutableSet.builder();
final Set<Map.Entry<String, String>> activeContexts = getActiveContexts();
ret.addAll(Iterables.transform(subj.getPermissions(activeContexts).asMap().entrySet(),
input -> new PermissionAttachmentInfo(player, input.getKey(), null, input.getValue() > 0)));
for (Metapermission mPerm : METAPERMISSIONS) {
ret.addAll(Iterators.transform(mPerm.getValues(this.subj, activeContexts), input -> new PermissionAttachmentInfo(player, input, null, true)));
}
return ret.build();
}
public Set<Map.Entry<String, String>> getActiveContexts() {
ImmutableSet.Builder<Map.Entry<String, String>> builder = ImmutableSet.builder();
builder.add(Maps.immutableEntry("world", player.getWorld().getName()));
builder.add(Maps.immutableEntry("dimension", player.getWorld().getEnvironment().name().toLowerCase()));
for (String serverTag : plugin.getManager().getConfig().getServerTags()) {
builder.add(Maps.immutableEntry(PermissionsExPlugin.SERVER_TAG_CONTEXT, serverTag));
}
return builder.build();
}
public void setPreviousPermissible(Permissible previousPermissible) {
this.previousPermissible = previousPermissible;
}
public Permissible getPreviousPermissible() {
return previousPermissible;
}
}