-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCustomProviderTemplate.java
More file actions
300 lines (264 loc) · 10.5 KB
/
CustomProviderTemplate.java
File metadata and controls
300 lines (264 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package com.example.mypermissions;
import com.hypixel.hytale.server.core.permissions.provider.PermissionProvider;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Custom Permission Provider Template
*
* This template provides a starting point for implementing a custom permission provider.
* Replace the in-memory storage with your preferred backend (database, config, etc.).
*
* Usage:
* PermissionsModule.get().addProvider(new MyPermissionProvider());
*
* Important considerations:
* - All methods must be thread-safe (multiple threads may call simultaneously)
* - Return empty sets, never null
* - The first registered provider receives all write operations
* - Permission checks iterate through ALL providers
*
* Thread Safety Note:
* This template uses ConcurrentHashMap for basic thread safety, but the returned
* Sets may have race conditions if callers iterate while another thread modifies.
* The vanilla HytalePermissionsProvider uses ReadWriteLock, which is more robust
* for this use case. For production code, consider:
* - Using ReadWriteLock (like vanilla) instead of ConcurrentHashMap
* - Returning defensive copies or unmodifiable views from getter methods
* - Using Set.copyOf() for snapshot isolation
*/
public class CustomProviderTemplate implements PermissionProvider {
// =========================================================================
// STORAGE - Replace with your backend (database, config file, etc.)
// =========================================================================
// User UUID -> Set of permission nodes
private final Map<UUID, Set<String>> userPermissions = new ConcurrentHashMap<>();
// Group name -> Set of permission nodes
private final Map<String, Set<String>> groupPermissions = new ConcurrentHashMap<>();
// User UUID -> Set of group names
private final Map<UUID, Set<String>> userGroups = new ConcurrentHashMap<>();
// =========================================================================
// IDENTITY
// =========================================================================
/**
* Returns the unique name of this provider.
* Used in command output (e.g., /perm user list) to identify which provider
* returned which permissions.
*/
@Nonnull
@Override
public String getName() {
return "CustomProviderTemplate";
}
// =========================================================================
// USER PERMISSIONS
// =========================================================================
/**
* Add permissions directly to a user.
*
* @param uuid The user's UUID
* @param permissions Set of permission nodes to add (e.g., "my.perm", "other.perm")
*/
@Override
public void addUserPermissions(@Nonnull UUID uuid, @Nonnull Set<String> permissions) {
userPermissions.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet())
.addAll(permissions);
// TODO: Persist to your backend
// saveToDatabase(uuid, permissions);
}
/**
* Remove permissions from a user.
*
* @param uuid The user's UUID
* @param permissions Set of permission nodes to remove
*/
@Override
public void removeUserPermissions(@Nonnull UUID uuid, @Nonnull Set<String> permissions) {
Set<String> userPerms = userPermissions.get(uuid);
if (userPerms != null) {
userPerms.removeAll(permissions);
if (userPerms.isEmpty()) {
userPermissions.remove(uuid);
}
}
// TODO: Persist to your backend
// removeFromDatabase(uuid, permissions);
}
/**
* Get all direct permissions for a user.
* Does NOT include permissions inherited from groups.
*
* @param uuid The user's UUID
* @return Unmodifiable set of permission nodes (never null)
*/
@Nonnull
@Override
public Set<String> getUserPermissions(@Nonnull UUID uuid) {
Set<String> perms = userPermissions.get(uuid);
if (perms != null) {
return Collections.unmodifiableSet(perms);
}
return Collections.emptySet();
}
// =========================================================================
// GROUP PERMISSIONS
// =========================================================================
/**
* Add permissions to a group.
* All users in this group will inherit these permissions.
*
* @param group The group name (e.g., "VIP", "Moderator")
* @param permissions Set of permission nodes to add
*/
@Override
public void addGroupPermissions(@Nonnull String group, @Nonnull Set<String> permissions) {
groupPermissions.computeIfAbsent(group, k -> ConcurrentHashMap.newKeySet())
.addAll(permissions);
// TODO: Persist to your backend
}
/**
* Remove permissions from a group.
*
* @param group The group name
* @param permissions Set of permission nodes to remove
*/
@Override
public void removeGroupPermissions(@Nonnull String group, @Nonnull Set<String> permissions) {
Set<String> groupPerms = groupPermissions.get(group);
if (groupPerms != null) {
groupPerms.removeAll(permissions);
if (groupPerms.isEmpty()) {
groupPermissions.remove(group);
}
}
// TODO: Persist to your backend
}
/**
* Get all permissions for a group.
*
* @param group The group name
* @return Unmodifiable set of permission nodes (never null)
*/
@Nonnull
@Override
public Set<String> getGroupPermissions(@Nonnull String group) {
Set<String> perms = groupPermissions.get(group);
if (perms != null) {
return Collections.unmodifiableSet(perms);
}
return Collections.emptySet();
}
// =========================================================================
// USER-GROUP MEMBERSHIP
// =========================================================================
/**
* Add a user to a group.
* The user will inherit all permissions from that group.
*
* @param uuid The user's UUID
* @param group The group name to add the user to
*/
@Override
public void addUserToGroup(@Nonnull UUID uuid, @Nonnull String group) {
userGroups.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet())
.add(group);
// TODO: Persist to your backend
}
/**
* Remove a user from a group.
*
* @param uuid The user's UUID
* @param group The group name to remove the user from
*/
@Override
public void removeUserFromGroup(@Nonnull UUID uuid, @Nonnull String group) {
Set<String> groups = userGroups.get(uuid);
if (groups != null) {
groups.remove(group);
if (groups.isEmpty()) {
userGroups.remove(uuid);
}
}
// TODO: Persist to your backend
}
/**
* Get all groups a user belongs to.
*
* Note: The vanilla provider returns ["Default"] for users without explicit groups.
* Returning empty vs ["Default"] has different effects because
* PermissionsModule.getGroupsForUser() aggregates non-empty sets from ALL providers.
*
* If this provider returns empty and the vanilla provider returns ["Default"],
* the user ends up in ["Default"]. If this provider returns ["VIP"] and vanilla
* returns ["Default"], the user ends up in ["Default", "VIP"]. Choose your
* behavior carefully based on whether vanilla is also active.
*
* @param uuid The user's UUID
* @return Unmodifiable set of group names (never null)
*/
@Nonnull
@Override
public Set<String> getGroupsForUser(@Nonnull UUID uuid) {
Set<String> groups = userGroups.get(uuid);
if (groups != null && !groups.isEmpty()) {
return Collections.unmodifiableSet(groups);
}
// Option 1: Return empty (let other providers handle defaults)
// Use this when running alongside vanilla provider
return Collections.emptySet();
// Option 2: Return default group (like vanilla)
// Use this when replacing the vanilla provider entirely
// return Set.of("Default");
}
// =========================================================================
// OPTIONAL: Additional utility methods for your implementation
// =========================================================================
/**
* Check if a user has a specific permission (convenience method).
* Note: This bypasses the PermissionsModule resolution algorithm.
* For full resolution, use PermissionsModule.get().hasPermission() instead.
*/
public boolean hasDirectPermission(UUID uuid, String permission) {
return getUserPermissions(uuid).contains(permission);
}
/**
* Get all effective permissions for a user (direct + inherited from groups).
* Note: This is a convenience method; PermissionsModule handles resolution.
*/
public Set<String> getEffectivePermissions(UUID uuid) {
Set<String> effective = new HashSet<>(getUserPermissions(uuid));
for (String group : getGroupsForUser(uuid)) {
effective.addAll(getGroupPermissions(group));
}
return Collections.unmodifiableSet(effective);
}
/**
* Clear all data (useful for testing or reset functionality).
*/
public void clearAll() {
userPermissions.clear();
groupPermissions.clear();
userGroups.clear();
}
// =========================================================================
// OPTIONAL: Lifecycle methods (if extending JavaPlugin)
// =========================================================================
/**
* Called when the provider should load data from backend.
* Implement this if you need async loading from a database.
*/
public void load() {
// TODO: Load from database/config
// userPermissions.putAll(loadUserPermissionsFromDb());
// groupPermissions.putAll(loadGroupPermissionsFromDb());
// userGroups.putAll(loadUserGroupsFromDb());
}
/**
* Called when the provider should save all data to backend.
* Implement this for graceful shutdown.
*/
public void save() {
// TODO: Save to database/config
// saveAllToDatabase();
}
}