Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Permissions API #229

Draft
wants to merge 59 commits into
base: dev/v2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
11e0492
start permissions api
MrIvanPlays Nov 22, 2022
2dba238
more foundation
MrIvanPlays Nov 22, 2022
6fde59f
allNodesWithContext
MrIvanPlays Nov 22, 2022
3cea14e
add weight
MrIvanPlays Nov 22, 2022
5856160
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 23, 2022
4ca5ce8
progress
MrIvanPlays Nov 23, 2022
a12c1b3
bring some consistency in NodeFactory
MrIvanPlays Nov 23, 2022
2978084
retrieveHeldContexts
MrIvanPlays Nov 23, 2022
94defc8
simplify space check
MrIvanPlays Nov 24, 2022
4cbbc25
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 27, 2022
73f0695
Merge branch 'new/permissions-api' of https://github.com/ArcanePlugin…
MrIvanPlays Nov 27, 2022
e698aeb
add removeNode
MrIvanPlays Nov 27, 2022
4afceec
allNodesKeys
MrIvanPlays Nov 27, 2022
c62b477
parentNodeHolder
MrIvanPlays Nov 27, 2022
bb411e9
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 27, 2022
9aee46c
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 28, 2022
7779ac5
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 28, 2022
5b399c6
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 28, 2022
4082e78
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 29, 2022
4abc1ed
add missing annotation so that github actions builds
MrIvanPlays Nov 29, 2022
5fcbdd7
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 29, 2022
16eb615
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 29, 2022
06b8cc2
NodeHolderGroup
MrIvanPlays Nov 29, 2022
49f8120
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Nov 30, 2022
c102ce0
require context to be specified for retrieve and insertOrModify
MrIvanPlays Nov 30, 2022
ddef41d
also for removeNode
MrIvanPlays Nov 30, 2022
213884c
remove hasNode(Node<?>)
MrIvanPlays Nov 30, 2022
4d47ce8
group methods
MrIvanPlays Nov 30, 2022
46e594a
do not hardcode contexts and types
MrIvanPlays Nov 30, 2022
a966915
fix node factory
MrIvanPlays Nov 30, 2022
5b13b61
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Dec 1, 2022
cee38b7
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Dec 5, 2022
3726e34
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Dec 11, 2022
067f29b
remove ContextSet#registerContext and document it
MrIvanPlays Dec 11, 2022
2d79a88
remove "hasNode"
MrIvanPlays Dec 11, 2022
adb9c9a
make ContextSet iterable
MrIvanPlays Dec 11, 2022
98269eb
specify nullability
MrIvanPlays Dec 11, 2022
3891a02
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Dec 26, 2022
3a7eb23
basic events
MrIvanPlays Dec 26, 2022
68e90f3
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Jan 7, 2023
eb81eae
use namespacedkey here too
MrIvanPlays Jan 7, 2023
dac47d7
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Feb 2, 2023
f37c380
some small changes
MrIvanPlays Feb 2, 2023
235db89
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Feb 16, 2023
82bb05e
change weight to primitive value
MrIvanPlays Feb 16, 2023
7159a0f
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Apr 5, 2023
789878c
Update with dev/2.0.0
MrIvanPlays Apr 5, 2023
f633b43
rework nodes
MrIvanPlays Apr 9, 2023
2c870d2
some changes
MrIvanPlays Apr 10, 2023
49ce899
forgot annos, whoops
MrIvanPlays Apr 10, 2023
46ef940
Merge branch 'dev/2.0.0' of https://github.com/ArcanePlugins/Treasury…
MrIvanPlays Apr 26, 2023
4bd2284
Transform NodeEvent
MrIvanPlays Apr 26, 2023
afe4506
Update constructors of delegating events
MrIvanPlays Apr 26, 2023
3deb463
Merge branch 'dev/v2' of https://github.com/ArcanePlugins/Treasury in…
MrIvanPlays Jul 2, 2023
8817874
Merge branch 'dev/v2' of https://github.com/ArcanePlugins/Treasury in…
MrIvanPlays Jul 6, 2023
c0540a4
Write some javadocs
MrIvanPlays Jul 6, 2023
e235362
NotNull
MrIvanPlays Jul 6, 2023
516f31d
Merge branch 'dev/v2' of https://github.com/ArcanePlugins/Treasury in…
MrIvanPlays Jul 11, 2023
9c2d517
Document Node.Builder
MrIvanPlays Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission;

import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import me.lokka30.treasury.api.common.response.Response;
import me.lokka30.treasury.api.permission.node.NodeFactory;
import me.lokka30.treasury.api.permission.node.holder.NodeHolderGroup;
import me.lokka30.treasury.api.permission.node.holder.NodeHolderPlayer;
import org.jetbrains.annotations.NotNull;

public interface PermissionsProvider {

@NotNull NodeFactory nodeFactory();

@NotNull CompletableFuture<Response<NodeHolderPlayer>> retrievePlayerNodeHolder(@NotNull UUID uuid);

@NotNull CompletableFuture<Response<Collection<UUID>>> allPlayerNodeHolderIds();

@NotNull CompletableFuture<Response<NodeHolderGroup>> retrieveOrCreateGroup(@NotNull String groupId);

@NotNull CompletableFuture<Response<Collection<String>>> allGroupNodeHolderIds();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.context;

import java.util.Objects;
import java.util.function.BiPredicate;
import me.lokka30.treasury.api.permission.node.holder.NodeHolder;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

public interface Context {

Context GLOBAL = Context.of("global", ($, $1) -> true);

@Contract(value = "_, _ -> new", pure = true)
@NotNull
static Context of(@NotNull String key, @NotNull BiPredicate<NodeHolder, String> condition) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(condition, "condition");
return new Context() {
@Override
public @NotNull String getKey() {
return key;
}

@Override
public @NotNull BiPredicate<NodeHolder, String> getCondition() {
return condition;
}
};
}

@NotNull String getKey();

@NotNull BiPredicate<NodeHolder, String> getCondition();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.context;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

public final class ContextSet {

private static final ContextSet EMPTY = new ContextSet(Collections.emptyMap());

@NotNull
public static ContextSet empty() {
return EMPTY;
}

@NotNull
public static ContextSet of(@NotNull Context @NotNull ... contexts) {
Objects.requireNonNull(contexts, "contexts");
if (contexts.length == 0) {
return empty();
}
Map<String, Context> map = new ConcurrentHashMap<>();
for (Context context : contexts) {
map.put(context.getKey(), context);
}
return new ContextSet(map);
}

private Map<String, Context> contexts;

@Contract(pure = true)
private ContextSet(Map<String, Context> contexts) {
this.contexts = contexts;
}

@NotNull
public Optional<Context> getContext(@NotNull String contextKey) {
Objects.requireNonNull(contextKey, "contextKey");
return Optional.ofNullable(this.contexts.get(contextKey));
}

public void registerContext(@NotNull Context context) {
MrIvanPlays marked this conversation as resolved.
Show resolved Hide resolved
Objects.requireNonNull(context, "context");
if (!this.contexts.containsKey(context.getKey())) {
this.contexts.put(context.getKey(), context);
return;
}
throw new IllegalArgumentException("Context already registered in this context set");
}

public boolean hasContext(@NotNull String contextKey) {
Objects.requireNonNull(contextKey, "contextKey");
return contexts.containsKey(contextKey);
}

@NotNull
public Set<Context> asSet() {
if (this.contexts.isEmpty()) {
return Collections.emptySet();
}
Set<Context> ret = ConcurrentHashMap.newKeySet();
ret.addAll(this.contexts.values());
return ret;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node;

import me.lokka30.treasury.api.permission.context.ContextSet;
import me.lokka30.treasury.api.permission.node.type.NodeType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface Node<Data> {

@NotNull NodeType<Data> nodeType();

@NotNull ContextSet contexts();

@Nullable Integer weight();
MrIvanPlays marked this conversation as resolved.
Show resolved Hide resolved

@NotNull String key();

@NotNull Data data();

@NotNull Node<Data> copyWithNewData(
@NotNull ContextSet contexts, @Nullable Integer weight, @NotNull Data data
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node;

import me.lokka30.treasury.api.common.misc.TriState;
import me.lokka30.treasury.api.permission.context.Context;
import me.lokka30.treasury.api.permission.context.ContextSet;
import me.lokka30.treasury.api.permission.node.type.PermissionNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public abstract class NodeFactory {

@NotNull
public PermissionNode createPermissionNode(@NotNull String permission) {
return this.createPermissionNode(permission, ContextSet.of(Context.GLOBAL));
}

@NotNull
public PermissionNode createPermissionNode(
@NotNull String permission, @NotNull ContextSet contextSet
) {
return this.createPermissionNode(permission, contextSet, (Integer) null);
}

@NotNull
public PermissionNode createPermissionNode(
@NotNull String permission, @NotNull ContextSet contextSet, @Nullable Integer weight
) {
return this.createPermissionNode(permission, contextSet, weight, TriState.UNSPECIFIED);
}

@NotNull
public PermissionNode createPermissionNode(
@NotNull String permission, @NotNull ContextSet contextSet, @NotNull TriState value
) {
return this.createPermissionNode(permission, contextSet, null, value);
}

@NotNull
public abstract PermissionNode createPermissionNode(
@NotNull String permission,
@NotNull ContextSet contextSet,
@Nullable Integer weight,
@NotNull TriState value
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node.holder;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import me.lokka30.treasury.api.common.misc.TriState;
import me.lokka30.treasury.api.common.response.Response;
import me.lokka30.treasury.api.permission.node.Node;
import me.lokka30.treasury.api.permission.node.type.NodeType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface NodeHolder {

@NotNull String getIdentifier();

@Nullable NodeHolder parentNodeHolder();

@NotNull CompletableFuture<Response<Collection<Node<?>>>> allNodes();

@NotNull CompletableFuture<Response<Map<String, NodeType<?>>>> allNodesKeys();

@NotNull <Data> CompletableFuture<Response<Collection<Node<Data>>>> allNodesWithType(@NotNull NodeType<Data> nodeType);

@NotNull CompletableFuture<Response<TriState>> hasNode(@NotNull String nodeKey);

@NotNull <Data> CompletableFuture<Response<Optional<Node<Data>>>> retrieveNode(
@NotNull String key, @NotNull NodeType<Data> nodeType
);

@NotNull <Data> CompletableFuture<Response<TriState>> insertOrModifyNode(
@NotNull Node<Data> node, @NotNull NodeType<Data> nodeType
);

@NotNull CompletableFuture<Response<TriState>> removeNode(@NotNull String nodeKey);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node.holder;

public interface NodeHolderGroup extends NodeHolder {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node.holder;

import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import me.lokka30.treasury.api.common.misc.TriState;
import me.lokka30.treasury.api.common.response.Response;
import me.lokka30.treasury.api.permission.context.ContextSet;
import me.lokka30.treasury.api.permission.node.Node;
import me.lokka30.treasury.api.permission.node.type.NodeType;
import org.jetbrains.annotations.NotNull;

public interface NodeHolderPlayer extends NodeHolder {

@NotNull UUID getUniqueId();

@Override
@NotNull
default String getIdentifier() {
return this.getUniqueId().toString();
}

/**
* Returns whether the specified {@link ContextSet context set} applies for this player node
* holder.
* <p>By "applies" it is meant that all the
* {@link me.lokka30.treasury.api.permission.context.Context contexts} defined in the
* specified context set's conditions are met.
*
* @param contextSet context set to probe
* @return applies or not.
*/
boolean contextSetApplies(@NotNull ContextSet contextSet);

@NotNull
default CompletableFuture<Response<TriState>> hasPermission(@NotNull String nodeKey) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether using CompletableFuture here is the best approach. Rather than have a data request be performed every time hasPermission is called, why not create a method to pre-load user permission data? The client plugin may request permission data for a user; once loaded, this permission data is in-memory so hasPermission calls using the data object return synchronously. I've never used the LuckPerms API but I believe it allows something like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that should not be in the hands of the API, and rather, the permissions service provider. Regardless, it would not be ideal to add a synchronous cache-assuming hasPermission method since that will be abused by developers instead of using the CF version, even when they are not certain that permissions have been cached for a user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of this idea? We might design the API so that it is impossible to use a cache-backed hasPermission without first loading such cache.

For example, a loadUser(UUID) method returns a CompletableFuture<LoadedUser>. Then, the synchronous LoadedUser#hasPermission allows checking permissions synchronously.

Copy link
Member

@lokka30 lokka30 Dec 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API could handle caching, though it is imperative that permission service provider developers fully understand that they must update the cache of a permission holder when some relevant part of their context changes.

For instance, if permission X is only given to player Y in world Z, and player Y changes to world W, then the cache should be updated primarily to ensure that permission X only remains allocated if the player remains in world Z.
(Whether this updates the entire cache, or just re-checks that individual permission, is another matter.)

Another consideration to make is that the rest of the APIs do not offer inbuilt caching. Ideally, we keep things consistent between our APIs.

Overall, I'm uncertain about that idea, and more inclined to think it would not be the best route forward.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current design allows for caching. Changing the design to something like loadUser will often lead to confusion between both permission plugins and plugins which use the api.

Consider this: plugin x uses the api and calls loadUser each time they need it, which causes multiple database queries to be performed, because developer y of plugin x doesn't know the implementation cache is being updated (if it really is, because permission plugin z may choose not to update it). The current design significantly reduces database queries, because one may not need the whole information about the account.

Objects.requireNonNull(nodeKey, "nodeKey");
return retrieveNode(nodeKey, NodeType.PERMISSION).thenApply(resp -> {
if (!resp.isSuccessful()) {
return Response.failure(resp.getFailureReason());
}
Optional<Node<TriState>> nodeOpt = resp.getResult();
if (!nodeOpt.isPresent()) {
return Response.success(TriState.UNSPECIFIED);
}
return Response.success(TriState.fromBoolean(this.contextSetApplies(nodeOpt
.get()
.contexts())));
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node.type;

import me.lokka30.treasury.api.common.misc.TriState;
import org.jetbrains.annotations.NotNull;

public final class NodeType<Data> {

public static final NodeType<TriState> PERMISSION = new NodeType<>(TriState.class);

private final Class<Data> dataClass;

private NodeType(@NotNull Class<Data> dataClass) {
this.dataClass = dataClass;
}

@NotNull
public Class<Data> dataClass() {
return this.dataClass;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This file is/was part of Treasury. To read more information about Treasury such as its licensing, see <https://github.com/ArcanePlugins/Treasury>.
*/

package me.lokka30.treasury.api.permission.node.type;

import me.lokka30.treasury.api.common.misc.TriState;
import me.lokka30.treasury.api.permission.node.Node;
import me.lokka30.treasury.api.permission.node.type.NodeType;
import org.jetbrains.annotations.NotNull;

public interface PermissionNode extends Node<TriState> {

@Override
@NotNull
default NodeType<TriState> nodeType() {
return NodeType.PERMISSION;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.lokka30.treasury.api.permission.node.type;

// todo
public interface PrefixNode {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.lokka30.treasury.api.permission.node.type;

// todo
public interface SuffixNode {

}