From 8bf3b758d7f9dfbd2db233fd10abeaa4059b5713 Mon Sep 17 00:00:00 2001 From: Mirco Ropic Date: Fri, 25 Jul 2025 14:16:58 +0000 Subject: [PATCH] Dateien nach "sourcecode/src/main/java/de/gmp/keycloak" hochladen --- .../java/de/gmp/keycloak/IpAuthenticator.java | 150 ++++++++++++++++++ .../gmp/keycloak/IpAuthenticatorFactory.java | 79 +++++++++ 2 files changed, 229 insertions(+) create mode 100644 sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticator.java create mode 100644 sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticatorFactory.java diff --git a/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticator.java b/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticator.java new file mode 100644 index 0000000..2645ffb --- /dev/null +++ b/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticator.java @@ -0,0 +1,150 @@ +package de.gmp.keycloak; + +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.apache.commons.net.util.SubnetUtils; +import java.util.Optional; +import java.util.stream.Stream; +import org.keycloak.authentication.AuthenticationFlowError; + +import java.net.InetAddress; +import java.net.UnknownHostException; + + +// import org.keycloak.sessions.AuthenticationSessionModel; +// import org.keycloak.storage.UserStorageManager; +// import org.keycloak.credential.CredentialModel; +// import org.keycloak.storage.UserStorageManager; +// import org.keycloak.models.utils.KeycloakModelUtils; +// import org.keycloak.models.UserCredentialModel; +// import org.keycloak.credential.UserCredentialStore; +// import org.keycloak.authentication.AuthenticationProcessor; +// import org.keycloak.authentication.AuthenticationFlowError; +// import org.keycloak.authentication.CredentialValidator; +// import org.keycloak.credential.CredentialProvider; +// import org.keycloak.models.KeycloakSession; +// import org.keycloak.models.RealmModel; +// import org.keycloak.models.UserCredentialModel; +// import org.keycloak.models.UserModel; +// import org.keycloak.models.utils.FormMessage; +// import org.keycloak.sessions.AuthenticationSessionModel; +// import java.util.List; + + +public class IpAuthenticator implements Authenticator { + + @Override + public void authenticate(AuthenticationFlowContext context) { + // SCHRITT 1: Prüfen, ob der Benutzer den IP-Login überspringen will. + String skip = context.getHttpRequest().getUri().getQueryParameters().getFirst("skip_ip_login"); + if ("true".equals(skip)) { + context.attempted(); + return; + } + + // SCHRITT 2: IP-Prüfung wie gehabt + UserModel user = findUserByIp(context); + if (user != null) { + // Benutzer gefunden -> Zeige unsere spezielle Login-Seite an. + Response challenge = context.form() + .setAttribute("user", user) + .createForm("ip-login-page.ftl"); + context.challenge(challenge); + } else { + // Kein passender Benutzer gefunden -> Fahre mit dem normalen Flow fort. + context.attempted(); + } + } + + @Override + public void action(AuthenticationFlowContext context) { + UserModel user = findUserByIp(context); + if (user != null) { + context.setUser(user); + context.success(); + } else { + // Sicherheitshalber, falls die IP sich zwischenzeitlich ändert. + context.failure(AuthenticationFlowError.UNKNOWN_USER); + } + } + + private UserModel findUserByIp(AuthenticationFlowContext context) { + RealmModel realm = context.getRealm(); + KeycloakSession session = context.getSession(); + String clientIp = context.getConnection().getRemoteAddr(); + + try (Stream userStream = session.users().searchForUserByUserAttributeStream(realm, "ipLogin", "1")) { + Optional matchingUser = userStream.filter(user -> { + Optional ipChecker = user.getAttributeStream("ipAddresses").filter(ipBlock -> { + String[] ipBlocks = ipBlock.split(","); + for (String ipOrCidr : ipBlocks) { + //System.out.println(ipOrCidr); + if (isIpMatch(clientIp, ipOrCidr.trim())) { + //System.out.println(clientIp + " match!"); + return true; + } + } + return false; + }).findFirst(); + if (ipChecker.isPresent()) { + //System.out.println("Check: "+ipChecker); + return true; + } else { + return false; + } + }).findFirst(); + + if (matchingUser.isPresent()) { + // Speichere den gefundenen Benutzer für den nächsten Schritt (action) + UserModel user = matchingUser.get(); + return user; + } + + } + return null; + } + + private boolean isIpMatch(String ip, String ipOrCidr) { + try { + if (ipOrCidr.contains("-")) { // Mehrere IPs nach Schreibweise 192.168.1.1-192.168.1.5 + String[] parts = ipOrCidr.split("-"); + String startIp = parts[0].trim(); + String endIp = parts[1].trim(); + try { + long ipToCheckLong = ipToLong(ip); + long startIpLong = ipToLong(startIp); + long endIpLong = ipToLong(endIp); + return ipToCheckLong >= startIpLong && ipToCheckLong <= endIpLong; + } catch (UnknownHostException e) { + e.printStackTrace(); + return false; + } + } else { // Davon ausgehend, dass es sich nur um eine Adresse handelt + return ipOrCidr.equals(ip); + } + } catch (IllegalArgumentException e) { + return false; + } + } + + private static long ipToLong(String ip) throws UnknownHostException { + InetAddress inetAddress = InetAddress.getByName(ip); + byte[] ipBytes = inetAddress.getAddress(); + long result = 0; + for (byte b : ipBytes) { + result = (result << 8) | (b & 0xFF); + } + return result; + } + + // Leere Methoden + @Override public boolean requiresUser() { return false; } + @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; } + @Override public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {} + @Override public void close() {} +} \ No newline at end of file diff --git a/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticatorFactory.java b/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticatorFactory.java new file mode 100644 index 0000000..a1685b5 --- /dev/null +++ b/sourcecode/src/main/java/de/gmp/keycloak/IpAuthenticatorFactory.java @@ -0,0 +1,79 @@ +package de.gmp.keycloak; + +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import java.util.List; +import java.util.Collections; + +// import org.keycloak.models.UserModel; +// import org.keycloak.models.RealmModel; + +public class IpAuthenticatorFactory implements AuthenticatorFactory { + + public static final String PROVIDER_ID = "ip-authenticator"; + + private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.ALTERNATIVE, + AuthenticationExecutionModel.Requirement.DISABLED + }; + + @Override + public String getDisplayType() { + return "Login per IP"; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public Authenticator create(KeycloakSession session) { + return new IpAuthenticator(); + } + + @Override + public void init(Config.Scope config) { } + + @Override + public void postInit(KeycloakSessionFactory factory) { } + + @Override + public void close() { } + + @Override + public boolean isConfigurable() { return false; } + + @Override + public boolean isUserSetupAllowed() { return false; } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[]{ + AuthenticationExecutionModel.Requirement.ALTERNATIVE, + AuthenticationExecutionModel.Requirement.DISABLED + }; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public List getConfigProperties() { + return Collections.emptyList(); + } + + @Override + public String getHelpText() { + return "Fügt eine 'Login mit 1 Klick'-Option hinzu, wenn die IP-Adresse bei den Attributen eines Benutzers gefunden wurde."; + } + + } \ No newline at end of file