Skip to content

Commit

Permalink
keycloak wip, dependencies bork
Browse files Browse the repository at this point in the history
  • Loading branch information
bazke committed Aug 31, 2024
1 parent 72d6ce5 commit b12b9ba
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 16 deletions.
28 changes: 28 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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'
}
Expand All @@ -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([
Expand All @@ -93,6 +119,8 @@ tasks.named('jar', Jar).configure {
"MixinConfigs": "ltpermissions.mixins.json"
])
}

from shadowJar.outputs.files
}

tasks.named('jarJar').configure {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ ltlib_version=[1.3,1.4)

org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
org.jline.terminal.dumb=false
2 changes: 2 additions & 0 deletions src/main/java/com/lovetropics/perms/LTPermissions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/lovetropics/perms/config/RolesConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +35,9 @@ public final class RolesConfig implements RoleProvider {
private final Role everyone;

private RolesConfig(List<Role> 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<String, Role> roleMap = ImmutableMap.builder();
for (Role role : roles) {
roleMap.put(role.id(), role);
Expand All @@ -47,6 +52,7 @@ public static RolesConfig get() {
}

public static List<String> setup() {
//todo load roles from keycloak
Path path = Paths.get("config/roles.json");
if (!Files.exists(path)) {
if (!createDefaultConfig(path)) {
Expand All @@ -60,6 +66,9 @@ public static List<String> 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));
Expand Down Expand Up @@ -90,6 +99,20 @@ private static boolean createDefaultConfig(Path path) {
}
}

private static <T> RolesConfig loadFromKeycloak(final KeycloakService keycloakService) {

List<String> 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 <T> RolesConfig parse(Dynamic<T> root, ConfigErrorConsumer error) {
RoleConfigMap roleConfigs = RoleConfigMap.parse(root, error);

Expand Down
87 changes: 87 additions & 0 deletions src/main/java/com/lovetropics/perms/keycloak/KeycloakService.java
Original file line number Diff line number Diff line change
@@ -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<UserRepresentation> search = getInstance().realm(realm)
.users().search(".tommi.");

//final List<String> 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<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Boolean> 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);

}
}
36 changes: 21 additions & 15 deletions src/main/java/com/lovetropics/perms/store/PlayerRoleManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<UUID, PlayerRoleSet> 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());
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -124,7 +130,7 @@ private void close(MinecraftServer server) {
this.onPlayerLeave(player);
}
} finally {
IOUtils.closeQuietly(this.database);
// IOUtils.closeQuietly(this.database);
}
}

Expand All @@ -139,7 +145,7 @@ public <R> R updateRoles(UUID uuid, Function<PlayerRoleSet, R> update) {
return update.apply(roles);
} finally {
if (roles.isDirty()) {
this.database.trySave(uuid, roles);
// this.database.trySave(uuid, roles);
}
}
}
Expand All @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/ltpermissions.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"refmap": "ltpermissions.refmap.json",
"mixins": [
"rule.LecternMenuMixin",
"rule.SignBlockMixin"
"rule.SignBlockMixin",
"rule.UserWhiteListMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down
15 changes: 15 additions & 0 deletions src/main/resources/worldedit-forge.mixins.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading

0 comments on commit b12b9ba

Please sign in to comment.