Skip to content

Commit fbcca4b

Browse files
committed
feat(users): user flags + partial offline users in whitelist and ops
1 parent 3c6810c commit fbcca4b

File tree

4 files changed

+126
-26
lines changed

4 files changed

+126
-26
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,15 +761,16 @@ 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 separated by ":"
773+
<username/UUID/ID>:flag1:flag2
773774
When input is a file, only one local file path or
774775
URL can be provided
775776
--connection-pool-max-idle-timeout=DURATION
@@ -798,6 +799,8 @@ Usage: mc-image-helper manage-users [-fh] [--existing=<existingFileBehavior>]
798799
Allowed: mojang, playerdb
799800
--version=<version> Minecraft game version. If not provided, assumes
800801
JSON format
802+
--offline Server is in offline mode, for users that have the
803+
offline flag the UUID is generated locally
801804
```
802805

803806
### maven-download

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

Lines changed: 27 additions & 22 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((String input) -> {return 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.name);
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.name)
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.name, 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.name);
218223
return JavaUser.builder()
219224
.uuid(uuid)
220225
// username needs to be present, but content doesn't matter
@@ -226,8 +231,8 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
226231

227232
// ...or username
228233
for (final JavaUser existingUser : existing) {
229-
if (existingUser.getName().equalsIgnoreCase(input)) {
230-
log.debug("Resolved '{}' from existing user entry by name: {}", input, existingUser);
234+
if (existingUser.getName().equalsIgnoreCase(user.name)) {
235+
log.debug("Resolved '{}' from existing user entry by name: {}", user.name, existingUser);
231236
return existingUser;
232237
}
233238
}
@@ -237,8 +242,8 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
237242
try {
238243
final List<JavaUser> userCache = objectMapper.readValue(userCacheFile.toFile(), LIST_OF_JAVA_USER);
239244
for (final JavaUser existingUser : userCache) {
240-
if (existingUser.getName().equalsIgnoreCase(input)) {
241-
log.debug("Resolved '{}' from user cache by name: {}", input, existingUser);
245+
if (existingUser.getName().equalsIgnoreCase(user.name)) {
246+
log.debug("Resolved '{}' from user cache by name: {}", user.name, existingUser);
242247
return existingUser;
243248
}
244249
}
@@ -247,8 +252,8 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
247252
}
248253
}
249254

250-
if (offline) {
251-
return getOfflineUUID(input);
255+
if (offline && user.flags.contains("offline")) {
256+
return getOfflineUUID(user.name);
252257
}
253258

254259
final UserApi userApi;
@@ -262,7 +267,7 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
262267
default:
263268
throw new GenericException("User API provider was not specified");
264269
}
265-
return userApi.resolveUser(input);
270+
return userApi.resolveUser(user.name);
266271

267272
});
268273

@@ -293,10 +298,10 @@ private Set<String> loadExistingTextUserList(Path resultFile) throws IOException
293298
return new HashSet<>();
294299
}
295300

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);
301+
private void verifyNotUuids(List<UserDef> userDefs) {
302+
for (final UserDef user : userDefs) {
303+
if (UuidQuirks.isIdOrUuid(user.name)) {
304+
throw new InvalidParameterException("UUID cannot be provided: " + user.name);
300305
}
301306
}
302307
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package me.itzg.helpers.users.model;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
7+
import lombok.Data;
8+
import lombok.experimental.SuperBuilder;
9+
import lombok.extern.jackson.Jacksonized;
10+
11+
@Data @SuperBuilder
12+
@Jacksonized
13+
public class UserDef {
14+
public final String name;
15+
public final List<String> flags;
16+
17+
public UserDef(String input) {
18+
ArrayList<String> tokens = new ArrayList<String>(Arrays.asList(input.split(":")));
19+
name = tokens.remove(0);
20+
flags = tokens;
21+
}
22+
}

src/test/java/me/itzg/helpers/users/ManageUsersCommandTest.java

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ void givenUuidsAndAllExist(WireMockRuntimeInfo wmInfo) throws IOException {
411411
@Nested
412412
public class whitelistOffline {
413413
@Test
414-
void givenNames(WireMockRuntimeInfo wmInfo) {
414+
void allOffline(WireMockRuntimeInfo wmInfo) {
415415
setupUserStubs();
416416

417417
final int exitCode = new CommandLine(
@@ -423,7 +423,7 @@ void givenNames(WireMockRuntimeInfo wmInfo) {
423423
"--offline",
424424
"--type", "JAVA_WHITELIST",
425425
"--output-directory", tempDir.toString(),
426-
"user1", "user2"
426+
"user1:offline", "user2:offline"
427427
);
428428

429429
assertThat(exitCode).isEqualTo(0);
@@ -445,6 +445,76 @@ void givenNames(WireMockRuntimeInfo wmInfo) {
445445
)
446446
);
447447
}
448+
@Test
449+
void allOnline(WireMockRuntimeInfo wmInfo) {
450+
setupUserStubs();
451+
452+
final int exitCode = new CommandLine(
453+
new ManageUsersCommand()
454+
)
455+
.execute(
456+
"--mojang-api-base-url", wmInfo.getHttpBaseUrl(),
457+
"--user-api-provider", "mojang",
458+
"--offline",
459+
"--type", "JAVA_WHITELIST",
460+
"--output-directory", tempDir.toString(),
461+
"user1", "user2"
462+
);
463+
464+
assertThat(exitCode).isEqualTo(0);
465+
466+
final Path expectedFile = tempDir.resolve("whitelist.json");
467+
468+
assertThat(expectedFile).exists();
469+
470+
assertJson(expectedFile)
471+
.isArrayContainingExactlyInAnyOrder(
472+
conditions()
473+
.satisfies(conditions()
474+
.at("/name").hasValue("user1")
475+
.at("/uuid").hasValue(USER1_UUID)
476+
)
477+
.satisfies(conditions()
478+
.at("/name").hasValue("user2")
479+
.at("/uuid").hasValue(USER2_UUID)
480+
)
481+
);
482+
}
483+
@Test
484+
void partialOnlineOffline(WireMockRuntimeInfo wmInfo) {
485+
setupUserStubs();
486+
487+
final int exitCode = new CommandLine(
488+
new ManageUsersCommand()
489+
)
490+
.execute(
491+
"--mojang-api-base-url", wmInfo.getHttpBaseUrl(),
492+
"--user-api-provider", "mojang",
493+
"--offline",
494+
"--type", "JAVA_WHITELIST",
495+
"--output-directory", tempDir.toString(),
496+
"user1:offline", "user2"
497+
);
498+
499+
assertThat(exitCode).isEqualTo(0);
500+
501+
final Path expectedFile = tempDir.resolve("whitelist.json");
502+
503+
assertThat(expectedFile).exists();
504+
505+
assertJson(expectedFile)
506+
.isArrayContainingExactlyInAnyOrder(
507+
conditions()
508+
.satisfies(conditions()
509+
.at("/name").hasValue("user1")
510+
.at("/uuid").hasValue(USER1_OFFLINE_UUID)
511+
)
512+
.satisfies(conditions()
513+
.at("/name").hasValue("user2")
514+
.at("/uuid").hasValue(USER2_UUID)
515+
)
516+
);
517+
}
448518
}
449519

450520
@Nested

0 commit comments

Comments
 (0)