diff --git a/build.gradle b/build.gradle index a1493d1..f82cd69 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'org.parchmentmc.librarian.forgegradle' version '1.+' id 'org.spongepowered.mixin' version '0.7+' + id 'com.github.johnrengelman.shadow' version '7.1.2' } jarJar.enable() @@ -68,6 +69,9 @@ dependencies { // implementation fg.deobf('com.github.hexomod:WorldEdit-CUI-FE3:1.16.5-3.0.9') + implementation 'org.keycloak:keycloak-admin-client:24.0.2' + + if (System.getProperty("idea.sync.active") != "true") { annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' } @@ -77,9 +81,31 @@ mixin { add sourceSets.main, "ltpermissions.refmap.json" } +shadowJar { + configurations = [project.configurations.implementation] + zip64 true + // Ensure that the shadowJar task includes the keycloak-admin dependency + relocate 'org.keycloak', 'shadow.org.keycloak' +} + +//jar { +// dependsOn shadowJar +// from { +// configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) } +// } +// // other configurations... +// // Ensure that the jar task includes the output of the shadowJar task +// from shadowJar.outputs.files +//} + tasks.named('jar', Jar).configure { archiveClassifier = 'slim' finalizedBy 'reobfJar' + dependsOn shadowJar +// dependsOn shadowJar +// from { +// configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) } +// } manifest { attributes([ @@ -93,6 +119,8 @@ tasks.named('jar', Jar).configure { "MixinConfigs": "ltpermissions.mixins.json" ]) } + + from shadowJar.outputs.files } tasks.named('jarJar').configure { diff --git a/gradle.properties b/gradle.properties index dea0185..5f32a0f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,3 +7,4 @@ ltlib_version=[1.3,1.4) org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false +org.jline.terminal.dumb=false \ No newline at end of file diff --git a/src/main/java/com/lovetropics/perms/LTPermissions.java b/src/main/java/com/lovetropics/perms/LTPermissions.java index 32ddd7d..5d06ccb 100644 --- a/src/main/java/com/lovetropics/perms/LTPermissions.java +++ b/src/main/java/com/lovetropics/perms/LTPermissions.java @@ -8,6 +8,7 @@ import com.lovetropics.perms.command.FlyCommand; import com.lovetropics.perms.command.RoleCommand; import com.lovetropics.perms.config.RolesConfig; +import com.lovetropics.perms.keycloak.KeycloakService; import com.lovetropics.perms.override.NameDecorationOverride; import com.lovetropics.perms.override.command.CommandOverride; import com.lovetropics.perms.protection.authority.shape.AuthorityShape; @@ -88,6 +89,7 @@ public RoleReader bySource(CommandSourceStack source) { }; public LTPermissions() { + KeycloakService.getInstance(); FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); MinecraftForge.EVENT_BUS.addListener(this::registerCommands); MinecraftForge.EVENT_BUS.addListener(this::onServerChat); diff --git a/src/main/java/com/lovetropics/perms/config/RolesConfig.java b/src/main/java/com/lovetropics/perms/config/RolesConfig.java index 32f304f..ff2ad86 100644 --- a/src/main/java/com/lovetropics/perms/config/RolesConfig.java +++ b/src/main/java/com/lovetropics/perms/config/RolesConfig.java @@ -9,10 +9,12 @@ import com.lovetropics.lib.permission.role.Role; import com.lovetropics.lib.permission.role.RoleProvider; import com.lovetropics.perms.LTPermissions; +import com.lovetropics.perms.keycloak.KeycloakService; import com.lovetropics.perms.role.SimpleRole; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; +import org.keycloak.admin.client.Keycloak; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -33,6 +35,9 @@ public final class RolesConfig implements RoleProvider { private final Role everyone; private RolesConfig(List roles, Role everyone) { + //TODO keycloak things in this class too. + //TODO or maybe not? this seems to configure what the roles are and what they can do. + //probably annoying to get from kc for now, but maybe later. ImmutableMap.Builder roleMap = ImmutableMap.builder(); for (Role role : roles) { roleMap.put(role.id(), role); @@ -47,6 +52,7 @@ public static RolesConfig get() { } public static List setup() { + //todo load roles from keycloak Path path = Paths.get("config/roles.json"); if (!Files.exists(path)) { if (!createDefaultConfig(path)) { @@ -60,6 +66,9 @@ public static List setup() { try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { JsonElement root = JsonParser.parseReader(reader); instance = parse(new Dynamic<>(JsonOps.INSTANCE, root), errorConsumer); + + //instead of parse() returning an instance, just figure out the roles here. but is it necessary to preload? maybe - for ingame stuff + PermissionsApi.setRoleProvider(instance); LTPermissions.LOGGER.debug("Loaded {} roles", instance.roles.size()); instance.roles.forEach((name, role) -> LTPermissions.LOGGER.debug("Role {} has configuration: {}", name, role)); @@ -90,6 +99,20 @@ private static boolean createDefaultConfig(Path path) { } } + private static RolesConfig loadFromKeycloak(final KeycloakService keycloakService) { + + List roles = new ArrayList<>(); + + /* + + use a role_config.json from server (at least initially). what is in there? need to map that file to roles in keycloak + + */ + + //todo + + return null; + } private static RolesConfig parse(Dynamic root, ConfigErrorConsumer error) { RoleConfigMap roleConfigs = RoleConfigMap.parse(root, error); diff --git a/src/main/java/com/lovetropics/perms/keycloak/KeycloakService.java b/src/main/java/com/lovetropics/perms/keycloak/KeycloakService.java new file mode 100644 index 0000000..822422a --- /dev/null +++ b/src/main/java/com/lovetropics/perms/keycloak/KeycloakService.java @@ -0,0 +1,87 @@ +package com.lovetropics.perms.keycloak; + +import com.mojang.authlib.GameProfile; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.List; +import java.util.stream.Stream; + +public class KeycloakService { + + private static Keycloak INSTANCE; + final static String serverUrl = "https://identity.lovetropics.org"; + final static String realm = "LoveTropics"; + final static String clientId = "lt-minecraft-query"; + final static String clientSecret = "secret"; + + public KeycloakService() { + + } + + public void getRolesForPlayer(final GameProfile gameProfile) { + getInstance().realm(realm) + .users() + .list() + .forEach(userRepresentation -> { + System.out.println(userRepresentation.getUsername()); + }); + } + + public static boolean hasAccessToLt24(final GameProfile gameProfile) { + + final List search = getInstance().realm(realm) + .users().search(".tommi."); + + //final List stringStream = getInstance().realm(realm).users().search(".tommi.").get(0).getGroups(); + + getInstance().realm(realm).users().get("10d21d80-08d1-4857-aa9b-5bdbc9bfdb1e").groups().stream() + .map(GroupRepresentation::getName) + .forEach(s -> System.out.println(s)); + + //.map(GroupRepresentation::getName).findFirst().map(Object::toString).orElse("NO_GROUP") + + getInstance().realm(realm) + .users() + .list() + .forEach(userRepresentation -> { + final String username = userRepresentation.getUsername(); + final List realmRoles = userRepresentation.getRealmRoles(); + //join realmRoles + + final String roles = realmRoles == null ? "" : String.join(", ", realmRoles); + System.out.println(username + " has roles: " + roles); + + }); + + + return true; + } + + public static Keycloak getInstance() { + if (INSTANCE == null) { + INSTANCE = createInstance(); + } + return INSTANCE; + } + + private static Keycloak createInstance() { + if (INSTANCE == null) { + INSTANCE = KeycloakBuilder.builder() + .serverUrl(serverUrl) + .grantType(OAuth2Constants.PASSWORD) + .realm("master") + .clientId("admin-cli") +// .clientSecret(clientSecret) + .resteasyClient(ResteasyClientBuilder.newClient()) + .username(clientId) + .password(clientSecret) + .build(); + } + return INSTANCE; + } +} \ No newline at end of file diff --git a/src/main/java/com/lovetropics/perms/mixin/rule/UserWhiteListMixin.java b/src/main/java/com/lovetropics/perms/mixin/rule/UserWhiteListMixin.java new file mode 100644 index 0000000..69c19a4 --- /dev/null +++ b/src/main/java/com/lovetropics/perms/mixin/rule/UserWhiteListMixin.java @@ -0,0 +1,33 @@ +package com.lovetropics.perms.mixin.rule; + +import com.lovetropics.perms.keycloak.KeycloakService; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.players.UserWhiteList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpRequest; + +@Mixin(UserWhiteList.class) +public class UserWhiteListMixin { + + @Inject(method = "isWhiteListed", at = @At("HEAD"), cancellable = true) + public void isWhiteListed(final GameProfile gameProfile, final CallbackInfoReturnable cir) throws URISyntaxException { +// cir.setReturnValue(false); + System.out.println("chekin yo whitelist he he he he"); + + final boolean hasAccess = KeycloakService.hasAccessToLt24(gameProfile); + + if (!hasAccess) { + System.out.println("User " + gameProfile.getName() + " does not have access to the server"); + } + + + cir.setReturnValue(hasAccess); + + } +} diff --git a/src/main/java/com/lovetropics/perms/store/PlayerRoleManager.java b/src/main/java/com/lovetropics/perms/store/PlayerRoleManager.java index 877a96c..9997374 100644 --- a/src/main/java/com/lovetropics/perms/store/PlayerRoleManager.java +++ b/src/main/java/com/lovetropics/perms/store/PlayerRoleManager.java @@ -3,6 +3,7 @@ import com.lovetropics.lib.permission.role.RoleReader; import com.lovetropics.perms.LTPermissions; import com.lovetropics.perms.config.RolesConfig; +import com.lovetropics.perms.keycloak.KeycloakService; import com.lovetropics.perms.store.db.PlayerRoleDatabase; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.server.MinecraftServer; @@ -27,16 +28,19 @@ public final class PlayerRoleManager { private static PlayerRoleManager instance; - private final PlayerRoleDatabase database; + // private final PlayerRoleDatabase database; + private final KeycloakService keycloakService; private final Map onlinePlayerRoles = new Object2ObjectOpenHashMap<>(); - private PlayerRoleManager(PlayerRoleDatabase database) { - this.database = database; + // private PlayerRoleManager(PlayerRoleDatabase database) { + private PlayerRoleManager(final KeycloakService keycloakService) { + this.keycloakService = keycloakService; } @SubscribeEvent public static void onServerStarting(ServerStartingEvent event) { + //TODO hook keycloak things in here maybe instance = PlayerRoleManager.open(event.getServer()); } @@ -66,13 +70,15 @@ public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { } private static PlayerRoleManager open(MinecraftServer server) { - try { - Path path = server.getWorldPath(LevelResource.PLAYER_DATA_DIR).resolve("player_roles"); - PlayerRoleDatabase database = PlayerRoleDatabase.open(path); - return new PlayerRoleManager(database); - } catch (IOException e) { - throw new RuntimeException("failed to open player roles database"); - } +// try { +// Path path = server.getWorldPath(LevelResource.PLAYER_DATA_DIR).resolve("player_roles"); +// PlayerRoleDatabase database = PlayerRoleDatabase.open(path); +// return new PlayerRoleManager(database); + KeycloakService keycloakService = new KeycloakService(); + return new PlayerRoleManager(keycloakService); +// } catch (IOException e) { +// throw new RuntimeException("failed to open player roles database"); +// } } public static PlayerRoleManager get() { @@ -83,14 +89,14 @@ public void onPlayerJoin(ServerPlayer player) { if (!this.onlinePlayerRoles.containsKey(player.getUUID())) { RolesConfig config = RolesConfig.get(); PlayerRoleSet roles = this.loadPlayerRoles(player, config); - this.database.tryLoadInto(player.getUUID(), roles); +// this.database.tryLoadInto(player.getUUID(), roles); } } public void onPlayerLeave(ServerPlayer player) { PlayerRoleSet roles = this.onlinePlayerRoles.remove(player.getUUID()); if (roles != null && roles.isDirty()) { - this.database.trySave(player.getUUID(), roles); +// this.database.trySave(player.getUUID(), roles); roles.setDirty(false); } } @@ -124,7 +130,7 @@ private void close(MinecraftServer server) { this.onPlayerLeave(player); } } finally { - IOUtils.closeQuietly(this.database); +// IOUtils.closeQuietly(this.database); } } @@ -139,7 +145,7 @@ public R updateRoles(UUID uuid, Function update) { return update.apply(roles); } finally { if (roles.isDirty()) { - this.database.trySave(uuid, roles); +// this.database.trySave(uuid, roles); } } } @@ -154,7 +160,7 @@ private PlayerRoleSet loadOfflinePlayerRoles(UUID uuid) { RolesConfig config = RolesConfig.get(); PlayerRoleSet roles = new PlayerRoleSet(config.everyone(), null); - this.database.tryLoadInto(uuid, roles); +// this.database.tryLoadInto(uuid, roles); return roles; } diff --git a/src/main/resources/ltpermissions.mixins.json b/src/main/resources/ltpermissions.mixins.json index 2808276..a5b8ff8 100644 --- a/src/main/resources/ltpermissions.mixins.json +++ b/src/main/resources/ltpermissions.mixins.json @@ -5,7 +5,8 @@ "refmap": "ltpermissions.refmap.json", "mixins": [ "rule.LecternMenuMixin", - "rule.SignBlockMixin" + "rule.SignBlockMixin", + "rule.UserWhiteListMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/worldedit-forge.mixins.json b/src/main/resources/worldedit-forge.mixins.json new file mode 100644 index 0000000..a5b8ff8 --- /dev/null +++ b/src/main/resources/worldedit-forge.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "com.lovetropics.perms.mixin", + "compatibilityLevel": "JAVA_17", + "refmap": "ltpermissions.refmap.json", + "mixins": [ + "rule.LecternMenuMixin", + "rule.SignBlockMixin", + "rule.UserWhiteListMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +} diff --git a/src/test/java/com/lovetropics/perms/keycloak/KeycloakServiceTest.java b/src/test/java/com/lovetropics/perms/keycloak/KeycloakServiceTest.java new file mode 100644 index 0000000..f7b276c --- /dev/null +++ b/src/test/java/com/lovetropics/perms/keycloak/KeycloakServiceTest.java @@ -0,0 +1,25 @@ +//package com.lovetropics.perms.keycloak; +// +//import com.mojang.authlib.GameProfile; +//import org.junit.jupiter.api.Test; +//import org.keycloak.OAuth2Constants; +//import org.keycloak.admin.client.Keycloak; +//import org.keycloak.admin.client.KeycloakBuilder; +// +//import java.util.UUID; +// +//public class KeycloakServiceTest { +// +// @Test +// void test() { +// +// final GameProfile bazkeProfile = new GameProfile(UUID.fromString("0d7b4ed4-197d-490d-945f-d6f3df09e70d"), "bazke"); +// +// final boolean b = KeycloakService.hasAccessToLt24(bazkeProfile); +// +// +// System.out.println(b); +// +// +// } +//} \ No newline at end of file