Skip to content

Commit f6108ca

Browse files
FedotCompotitzg
andauthored
user flags + partial offline users in whitelist and ops (#583)
Co-authored-by: Geoff Bourne <[email protected]>
1 parent 6963886 commit f6108ca

File tree

5 files changed

+368
-43
lines changed

5 files changed

+368
-43
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,15 +761,17 @@ Usage: mc-image-helper manage-users [-fh] [--existing=<existingFileBehavior>]
761761
>] -t=<type>
762762
[--user-api-provider=<userApiProvider>]
763763
[--version=<version>]
764+
[--offline]
764765
[[--http-response-timeout=DURATION]
765766
[--tls-handshake-timeout=DURATION]
766767
[--connection-pool-pending-acquire-timeout=D
767768
URATION]
768769
[--connection-pool-max-idle-timeout=DURATION
769770
]] [INPUT[,INPUT...]...]
770771
[INPUT[,INPUT...]...] One or more Mojang usernames, UUID, or ID (UUID
771-
without dashes); however, when offline, only
772-
UUID/IDs can be provided.
772+
without dashes); flags are listed after a colon
773+
separated by comma:
774+
<username/UUID/ID>:flag1,flag2
773775
When input is a file, only one local file path or
774776
URL can be provided
775777
--connection-pool-max-idle-timeout=DURATION
@@ -798,6 +800,8 @@ Usage: mc-image-helper manage-users [-fh] [--existing=<existingFileBehavior>]
798800
Allowed: mojang, playerdb
799801
--version=<version> Minecraft game version. If not provided, assumes
800802
JSON format
803+
--offline Server is in offline mode, for users that have the
804+
offline flag the UUID is generated locally
801805
```
802806

803807
### maven-download

src/main/java/me/itzg/helpers/users/ManageUsersCommand.java

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import java.util.Set;
1717
import java.util.UUID;
1818
import java.util.concurrent.Callable;
19+
import java.util.stream.Collectors;
20+
1921
import lombok.extern.slf4j.Slf4j;
2022
import me.itzg.helpers.errors.GenericException;
2123
import me.itzg.helpers.errors.InvalidParameterException;
@@ -26,6 +28,7 @@
2628
import me.itzg.helpers.json.ObjectMappers;
2729
import me.itzg.helpers.users.model.JavaOp;
2830
import me.itzg.helpers.users.model.JavaUser;
31+
import me.itzg.helpers.users.model.UserDef;
2932

3033
import org.apache.commons.codec.digest.DigestUtils;
3134
import org.apache.maven.artifact.versioning.ComparableVersion;
@@ -114,8 +117,9 @@ public Integer call() throws Exception {
114117
}
115118

116119
private void processJavaUserIdList(SharedFetch sharedFetch, List<String> inputs) throws IOException {
120+
List<UserDef> userDefs = inputs.stream().map(input -> new UserDef(input)).collect(Collectors.toList());
117121
if (usesTextUserList()) {
118-
verifyNotUuids(inputs);
122+
verifyNotUuids(userDefs);
119123

120124
final Path resultFile = outputDirectory.resolve(
121125
type == Type.JAVA_OPS ? "ops.txt" : "white-list.txt"
@@ -126,8 +130,9 @@ private void processJavaUserIdList(SharedFetch sharedFetch, List<String> inputs)
126130
}
127131

128132
final Set<String> users = loadExistingTextUserList(resultFile);
129-
130-
users.addAll(inputs);
133+
for (final UserDef user : userDefs) {
134+
users.add(user.getName());
135+
}
131136

132137
log.debug("Writing users list to {}: {}", resultFile, users);
133138
Files.write(resultFile, users);
@@ -142,7 +147,7 @@ private void processJavaUserIdList(SharedFetch sharedFetch, List<String> inputs)
142147
}
143148

144149
objectMapper.writeValue(resultFile.toFile(),
145-
reconcile(sharedFetch, inputs,
150+
reconcile(sharedFetch, userDefs,
146151
loadExistingJavaJson(resultFile)
147152
)
148153
);
@@ -158,7 +163,7 @@ private boolean handleSkipExistingFile(Path resultFile) {
158163
return false;
159164
}
160165

161-
private List<? extends JavaUser> reconcile(SharedFetch sharedFetch, List<String> inputs, List<? extends JavaUser> existing) {
166+
private List<? extends JavaUser> reconcile(SharedFetch sharedFetch, List<UserDef> userDefs, List<? extends JavaUser> existing) {
162167

163168
final List<JavaUser> reconciled;
164169
if (existingFileBehavior == ExistingFileBehavior.MERGE) {
@@ -168,8 +173,8 @@ private List<? extends JavaUser> reconcile(SharedFetch sharedFetch, List<String>
168173
reconciled = new ArrayList<>(inputs.size());
169174
}
170175

171-
for (final String input : inputs) {
172-
final JavaUser resolvedUser = resolveJavaUserId(sharedFetch, existing, input.trim());
176+
for (final UserDef userDef : userDefs) {
177+
final JavaUser resolvedUser = resolveJavaUserId(sharedFetch, existing, userDef);
173178

174179
if (existingFileBehavior == ExistingFileBehavior.SYNCHRONIZE
175180
|| !containsUserByUuid(reconciled, resolvedUser.getUuid())) {
@@ -203,18 +208,18 @@ private boolean containsUserByUuid(List<JavaUser> users, String uuid) {
203208
return false;
204209
}
205210

206-
private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaUser> existing, String input) {
211+
private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaUser> existing, UserDef user) {
207212

208-
return UuidQuirks.ifIdOrUuid(input)
213+
return UuidQuirks.ifIdOrUuid(user.getName())
209214
.map(uuid -> {
210215
for (final JavaUser existingUser : existing) {
211216
if (existingUser.getUuid().equalsIgnoreCase(uuid)) {
212-
log.debug("Resolved '{}' from existing user entry by UUID: {}", input, existingUser);
217+
log.debug("Resolved '{}' from existing user entry by UUID: {}", user.getName(), existingUser);
213218
return existingUser;
214219
}
215220
}
216221

217-
log.debug("Resolved '{}' into new user entry", input);
222+
log.debug("Resolved '{}' into new user entry", user.getName());
218223
return JavaUser.builder()
219224
.uuid(uuid)
220225
// username needs to be present, but content doesn't matter
@@ -223,34 +228,46 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
223228

224229
})
225230
.orElseGet(() -> {
231+
// User to be used
232+
JavaUser finalUser = null;
226233

227-
// ...or username
234+
// Try to find user in existing users list
228235
for (final JavaUser existingUser : existing) {
229-
if (existingUser.getName().equalsIgnoreCase(input)) {
230-
log.debug("Resolved '{}' from existing user entry by name: {}", input, existingUser);
231-
return existingUser;
236+
if (existingUser.getName().equalsIgnoreCase(user.getName())) {
237+
log.debug("Resolved '{}' from existing user entry by name: {}", user.getName(), existingUser);
238+
finalUser = existingUser;
232239
}
233240
}
234241

242+
// If existing user is not found, build a new one
243+
if (finalUser == null) {
244+
finalUser = JavaUser.builder().name(user.getName()).build();
245+
}
246+
247+
// User is not online, generating offline UUID
248+
if (offline && user.getFlags().contains("offline")) {
249+
log.debug("Resolved '{}' as offline user", user.getName());
250+
// update UUID keeping the other fields in case of existing user
251+
return finalUser.setUuid(getOfflineUUID(user.getName()));
252+
}
253+
235254
final Path userCacheFile = outputDirectory.resolve("usercache.json");
236255
if (Files.exists(userCacheFile)) {
237256
try {
238257
final List<JavaUser> userCache = objectMapper.readValue(userCacheFile.toFile(), LIST_OF_JAVA_USER);
239258
for (final JavaUser existingUser : userCache) {
240-
if (existingUser.getName().equalsIgnoreCase(input)) {
241-
log.debug("Resolved '{}' from user cache by name: {}", input, existingUser);
242-
return existingUser;
259+
if (existingUser.getName().equalsIgnoreCase(user.getName())) {
260+
log.debug("Resolved '{}' from user cache by name: {}", user.getName(), existingUser);
261+
// UUID from usercache.jsona are safe to use regardless of the user type
262+
// if a UUID is present here, user joined successfully with that UUID
263+
return finalUser.setUuid(existingUser.getUuid());
243264
}
244265
}
245266
} catch (IOException e) {
246267
log.error("Failed to parse usercache.json", e);
247268
}
248269
}
249270

250-
if (offline) {
251-
return getOfflineUUID(input);
252-
}
253-
254271
final UserApi userApi;
255272
switch (userApiProvider) {
256273
case mojang:
@@ -262,8 +279,14 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
262279
default:
263280
throw new GenericException("User API provider was not specified");
264281
}
265-
return userApi.resolveUser(input);
282+
JavaUser apiUser = userApi.resolveUser(user.getName());
266283

284+
if (finalUser != null) {
285+
return finalUser.setUuid(apiUser.getUuid());
286+
}
287+
else {
288+
return apiUser;
289+
}
267290
});
268291

269292
}
@@ -293,10 +316,10 @@ private Set<String> loadExistingTextUserList(Path resultFile) throws IOException
293316
return new HashSet<>();
294317
}
295318

296-
private void verifyNotUuids(List<String> inputs) {
297-
for (final String input : inputs) {
298-
if (UuidQuirks.isIdOrUuid(input)) {
299-
throw new InvalidParameterException("UUID cannot be provided: " + input);
319+
private void verifyNotUuids(List<UserDef> userDefs) {
320+
for (final UserDef user : userDefs) {
321+
if (UuidQuirks.isIdOrUuid(user.getName())) {
322+
throw new InvalidParameterException("UUID cannot be provided: " + user.getName());
300323
}
301324
}
302325
}
@@ -341,8 +364,8 @@ private boolean usesTextUserList() {
341364
return version != null && new ComparableVersion(version).compareTo(MIN_VERSION_USES_JSON) < 0;
342365
}
343366

344-
private static JavaUser getOfflineUUID(String username) {
345-
byte[] bytes = DigestUtils.md5("OfflinePlayer:"+username);
367+
private static String getOfflineUUID(String username) {
368+
byte[] bytes = DigestUtils.md5("OfflinePlayer:" + username);
346369

347370
// Force version = 3 (bits 12-15 of time_hi_and_version)
348371
bytes[6] &= 0x0F;
@@ -363,9 +386,6 @@ private static JavaUser getOfflineUUID(String username) {
363386
lsb = (lsb << 8) | (bytes[i] & 0xFF);
364387
}
365388

366-
return JavaUser.builder()
367-
.name(username)
368-
.uuid(new UUID(msb, lsb).toString())
369-
.build();
389+
return new UUID(msb, lsb).toString();
370390
}
371391
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package me.itzg.helpers.users.model;
22

33
import lombok.Data;
4+
import lombok.Setter;
45
import lombok.experimental.SuperBuilder;
56
import lombok.extern.jackson.Jacksonized;
67

7-
@Data @SuperBuilder
8+
@Data @Setter @SuperBuilder
89
@Jacksonized
910
public class JavaUser {
1011
final String name;
1112

12-
final String uuid;
13+
String uuid;
1314
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.itzg.helpers.users.model;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
7+
8+
import lombok.Data;
9+
import lombok.experimental.SuperBuilder;
10+
import lombok.extern.jackson.Jacksonized;
11+
12+
@Data
13+
@SuperBuilder
14+
@Jacksonized
15+
public class UserDef {
16+
final String name;
17+
final List<String> flags;
18+
19+
public UserDef(String input) {
20+
final int colonIndex = input.trim().indexOf(':');
21+
if (colonIndex < 0) {
22+
name = input.trim();
23+
flags = new ArrayList<>();
24+
return;
25+
}
26+
name = input.substring(0, colonIndex).trim();
27+
flags = Arrays.stream(input.substring(colonIndex + 1).split(","))
28+
.map(String::trim)
29+
.filter(flag -> !flag.isEmpty())
30+
.collect(Collectors.toList());
31+
}
32+
}

0 commit comments

Comments
 (0)