keycloak-aplcache

Details

diff --git a/core/src/main/java/org/keycloak/util/HostUtils.java b/core/src/main/java/org/keycloak/util/HostUtils.java
index fb1b29f..391964a 100644
--- a/core/src/main/java/org/keycloak/util/HostUtils.java
+++ b/core/src/main/java/org/keycloak/util/HostUtils.java
@@ -8,29 +8,67 @@ import java.net.UnknownHostException;
  */
 public class HostUtils {
 
+    // Best effort to find the most proper hostname of this server.
     public static String getHostName() {
-        String jbossHostName = System.getProperty("jboss.host.name");
-        if (jbossHostName != null) {
-            return jbossHostName;
-        } else {
-            try {
-                return InetAddress.getLocalHost().getHostName();
-            } catch (UnknownHostException uhe) {
-                throw new IllegalStateException(uhe);
-            }
-        }
+        return getHostNameImpl().trim().toLowerCase();
     }
 
     public static String getIpAddress() {
         try {
-            String jbossHostName = System.getProperty("jboss.host.name");
-            if (jbossHostName != null) {
-                return InetAddress.getByName(jbossHostName).getHostAddress();
-            } else {
-                return java.net.InetAddress.getLocalHost().getHostAddress();
-            }
+            String hostname = getHostName();
+            return InetAddress.getByName(hostname).getHostAddress();
         } catch (UnknownHostException uhe) {
             throw new IllegalStateException(uhe);
         }
     }
+
+    private static String getHostNameImpl() {
+        // Return bind address if available
+        String bindAddr = System.getProperty("jboss.bind.address");
+        if (bindAddr != null && !bindAddr.trim().equals("0.0.0.0")) {
+            return bindAddr;
+        }
+
+        // Fallback to qualified name
+        String qualifiedHostName = System.getProperty("jboss.qualified.host.name");
+        if (qualifiedHostName != null) {
+            return qualifiedHostName;
+        }
+
+        // If not on jboss env, let's try other possible fallbacks
+        // POSIX-like OSes including Mac should have this set
+        qualifiedHostName = System.getenv("HOSTNAME");
+        if (qualifiedHostName != null) {
+            return qualifiedHostName;
+        }
+
+        // Certain versions of Windows
+        qualifiedHostName = System.getenv("COMPUTERNAME");
+        if (qualifiedHostName != null) {
+            return qualifiedHostName;
+        }
+
+        try {
+            return NetworkUtils.canonize(getLocalHost().getHostName());
+        } catch (UnknownHostException uhe) {
+            uhe.printStackTrace();
+            return "unknown-host.unknown-domain";
+        }
+    }
+
+    /**
+     * Methods returns InetAddress for localhost
+     *
+     * @return InetAddress of the localhost
+     * @throws UnknownHostException if localhost could not be resolved
+     */
+    private static InetAddress getLocalHost() throws UnknownHostException {
+        InetAddress addr;
+        try {
+            addr = InetAddress.getLocalHost();
+        } catch (ArrayIndexOutOfBoundsException e) {  //this is workaround for mac osx bug see AS7-3223 and JGRP-1404
+            addr = InetAddress.getByName(null);
+        }
+        return addr;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/util/NetworkUtils.java b/core/src/main/java/org/keycloak/util/NetworkUtils.java
new file mode 100644
index 0000000..8d9bd15
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/NetworkUtils.java
@@ -0,0 +1,446 @@
+package org.keycloak.util;
+
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Utility methods related to networking.
+ *
+ * @author Brian Stansberry (c) 2011 Red Hat Inc.
+ */
+public class NetworkUtils {
+
+    private static final int MAX_GROUP_LENGTH = 4;
+    private static final int IPV6_LEN = 8;
+    private static final boolean can_bind_to_mcast_addr; // are we running on Linux ?
+
+    static {
+        can_bind_to_mcast_addr = checkForLinux() || checkForSolaris() || checkForHp();
+    }
+
+    public static String formatPossibleIpv6Address(String address) {
+        if(address == null) {
+            return null;
+        }
+        String ipv6Address;
+        if (address.startsWith("[") && address.endsWith("]")) {
+            ipv6Address = address.substring(0, address.lastIndexOf(']')).substring(1);
+        } else {
+            ipv6Address = address;
+        }
+        // Definitely not an IPv6, return untouched input.
+        if (!mayBeIPv6Address(ipv6Address)) {
+            return ipv6Address;
+        }
+        return '[' + canonize(ipv6Address) + ']';
+    }
+
+    /**
+     * <p>Convert IPv6 adress into RFC 5952 form.
+     * E.g. 2001:db8:0:1:0:0:0:1 -> 2001:db8:0:1::1</p>
+     *
+     * <p>Method is null safe, and if IPv4 address or host name is passed to the
+     * method it is returned wihout any processing.</p>
+     *
+     * <p>Method also supports IPv4 in IPv6 (e.g. 0:0:0:0:0:ffff:192.0.2.1 ->
+     * ::ffff:192.0.2.1), and zone ID (e.g. fe80:0:0:0:f0f0:c0c0:1919:1234%4
+     * -> fe80::f0f0:c0c0:1919:1234%4).</p>
+     *
+     * @param ipv6Address String representing valid IPv6 address.
+     * @return String representing IPv6 in canonical form.
+     * @throws IllegalArgumentException if IPv6 format is unacceptable.
+     */
+    public static String canonize(String ipv6Address) throws IllegalArgumentException {
+
+        if (ipv6Address == null) {
+            return null;
+        }
+
+        // Definitely not an IPv6, return untouched input.
+        if (!mayBeIPv6Address(ipv6Address)) {
+            return ipv6Address;
+        }
+
+        // Length without zone ID (%zone) or IPv4 address
+        int ipv6AddressLength = ipv6Address.length();
+        if (isIPv4AddressInIPv6(ipv6Address)) {
+            // IPv4 in IPv6
+            // e.g. 0:0:0:0:0:FFFF:127.0.0.1
+            int lastColonPos = ipv6Address.lastIndexOf(":");
+            int lastColonsPos = ipv6Address.lastIndexOf("::");
+            if (lastColonsPos >= 0 && lastColonPos == lastColonsPos + 1) {
+                // IPv6 part ends with two consecutive colons, last colon is part of IPv6 format.
+                // e.g. ::127.0.0.1
+                ipv6AddressLength = lastColonPos + 1;
+            } else {
+                // IPv6 part ends with only one colon, last colon is not part of IPv6 format.
+                // e.g. ::FFFF:127.0.0.1
+                ipv6AddressLength = lastColonPos;
+            }
+        } else if (ipv6Address.contains(":") && ipv6Address.contains("%")) {
+            // Zone ID
+            // e.g. fe80:0:0:0:f0f0:c0c0:1919:1234%4
+            ipv6AddressLength = ipv6Address.lastIndexOf("%");
+        }
+
+        StringBuilder result = new StringBuilder();
+        char [][] groups = new char[IPV6_LEN][MAX_GROUP_LENGTH];
+        int groupCounter = 0;
+        int charInGroupCounter = 0;
+
+        // Index of the current zeroGroup, -1 means not found.
+        int zeroGroupIndex = -1;
+        int zeroGroupLength = 0;
+
+        // maximum length zero group, if there is more then one, then first one
+        int maxZeroGroupIndex = -1;
+        int maxZeroGroupLength = 0;
+
+        boolean isZero = true;
+        boolean groupStart = true;
+
+        /*
+         *  Two consecutive colons, initial expansion.
+         *  e.g. 2001:db8:0:0:1::1 -> 2001:db8:0:0:1:0:0:1
+         */
+        StringBuilder expanded = new StringBuilder(ipv6Address);
+        int colonsPos = ipv6Address.indexOf("::");
+        int length = ipv6AddressLength;
+        int change = 0;
+
+        if (colonsPos >= 0 && colonsPos < ipv6AddressLength - 2) {
+            int colonCounter = 0;
+            for (int i = 0; i < ipv6AddressLength; i++) {
+                if (ipv6Address.charAt(i) == ':') {
+                    colonCounter++;
+                }
+            }
+
+            if (colonsPos == 0) {
+                expanded.insert(0, "0");
+                change = change + 1;
+            }
+
+            for (int i = 0; i < IPV6_LEN - colonCounter; i++) {
+                expanded.insert(colonsPos + 1, "0:");
+                change = change + 2;
+            }
+
+
+            if (colonsPos == ipv6AddressLength - 2) {
+                expanded.setCharAt(colonsPos + change + 1, '0');
+            } else {
+                expanded.deleteCharAt(colonsPos + change + 1);
+                change = change - 1;
+            }
+            length = length + change;
+        }
+
+
+        // Processing one char at the time
+        for (int charCounter = 0; charCounter < length; charCounter++) {
+            char c = expanded.charAt(charCounter);
+            if (c >= 'A' && c <= 'F') {
+                c = (char) (c + 32);
+            }
+            if (c != ':') {
+                groups[groupCounter][charInGroupCounter] = c;
+                if (!(groupStart && c == '0')) {
+                    ++charInGroupCounter;
+                    groupStart = false;
+                }
+                if (c != '0') {
+                    isZero = false;
+                }
+            }
+            if (c == ':' || charCounter == (length - 1)) {
+                // We reached end of current group
+                if (isZero) {
+                    ++zeroGroupLength;
+                    if (zeroGroupIndex == -1) {
+                        zeroGroupIndex = groupCounter;
+                    }
+                }
+
+                if (!isZero || charCounter == (length - 1)) {
+                    // We reached end of zero group
+                    if (zeroGroupLength > maxZeroGroupLength) {
+                        maxZeroGroupLength = zeroGroupLength;
+                        maxZeroGroupIndex = zeroGroupIndex;
+                    }
+                    zeroGroupLength = 0;
+                    zeroGroupIndex = -1;
+                }
+                ++groupCounter;
+                charInGroupCounter = 0;
+                isZero = true;
+                groupStart = true;
+            }
+        }
+
+        int numberOfGroups = groupCounter;
+
+        // Output results
+        for (groupCounter = 0; groupCounter < numberOfGroups; groupCounter++) {
+            if (maxZeroGroupLength <= 1 || groupCounter < maxZeroGroupIndex
+                    || groupCounter >= maxZeroGroupIndex + maxZeroGroupLength) {
+                for (int j = 0; j < MAX_GROUP_LENGTH; j++) {
+                    if (groups[groupCounter][j] != 0) {
+                        result.append(groups[groupCounter][j]);
+                    }
+                }
+                if (groupCounter < (numberOfGroups - 1)
+                        && (groupCounter != maxZeroGroupIndex - 1
+                        || maxZeroGroupLength <= 1)) {
+                    result.append(':');
+                }
+            } else if (groupCounter == maxZeroGroupIndex) {
+                result.append("::");
+            }
+        }
+
+        // Solve problem with three colons in IPv4 in IPv6 format
+        // e.g. 0:0:0:0:0:0:127.0.0.1 -> :::127.0.0.1 -> ::127.0.0.1
+        int resultLength = result.length();
+        if (result.charAt(resultLength - 1) == ':' && ipv6AddressLength < ipv6Address.length()
+                && ipv6Address.charAt(ipv6AddressLength) == ':') {
+            result.delete(resultLength - 1, resultLength);
+        }
+
+        /*
+         * Append IPv4 from IPv4-in-IPv6 format or Zone ID
+         */
+        for (int i = ipv6AddressLength; i < ipv6Address.length(); i++) {
+            result.append(ipv6Address.charAt(i));
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Heuristic check if string might be an IPv6 address.
+     *
+     * @param input Any string or null
+     * @return true, if input string contains only hex digits and at least two colons, before '.' or '%' character.
+     */
+    private static boolean mayBeIPv6Address(String input) {
+        if (input == null) {
+            return false;
+        }
+
+        boolean result = false;
+        int colonsCounter = 0;
+        int length = input.length();
+        for (int i = 0; i < length; i++) {
+            char c = input.charAt(i);
+            if (c == '.' || c == '%') {
+                // IPv4 in IPv6 or Zone ID detected, end of checking.
+                break;
+            }
+            if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
+                    || (c >= 'A' && c <= 'F') || c == ':')) {
+                return false;
+            } else if (c == ':') {
+                colonsCounter++;
+            }
+        }
+        if (colonsCounter >= 2) {
+            result = true;
+        }
+        return result;
+    }
+    /**
+     * Check if it is an IPv4 in IPv6 format.
+     * e.g. 0:0:0:0:0:FFFF:127.0.0.1
+     *
+     * @param ipv6Address the address
+     * @return true, if input string is an IPv4 address in IPv6 format.
+     */
+    private static boolean isIPv4AddressInIPv6(String ipv6Address) {
+        return (ipv6Address.contains(":") && ipv6Address.contains("."));
+    }
+
+    /**
+     * Formats input address. For IPV4 returns simply host address, for IPV6 formats address according to <a
+     * href="http://tools.ietf.org/html/rfc5952">RFC5952</a> rules. It does not embed IPV6 address in '[', ']', since those are part of IPV6 URI literal.
+     *
+     * @param inet
+     * @return
+     */
+    public static String formatAddress(InetAddress inet){
+        if(inet == null){
+            throw new NullPointerException();
+        }
+        if(inet instanceof Inet4Address){
+            return inet.getHostAddress();
+        } else if (inet instanceof Inet6Address){
+            byte[] byteRepresentation = inet.getAddress();
+            int[] hexRepresentation = new int[IPV6_LEN];
+
+            for(int i=0;i < hexRepresentation.length;i++){
+                hexRepresentation[i] = ( byteRepresentation[2*i] & 0xFF) << 8 | ( byteRepresentation[2*i+1] & 0xFF );
+            }
+            compactLongestZeroSequence(hexRepresentation);
+            return formatAddress6(hexRepresentation);
+        } else {
+            return inet.getHostAddress();
+        }
+    }
+
+    /**
+     * Converts socket address into string literal, which has form: 'address:port'. Example:<br>
+     * <ul>
+     *      <li>127.0.0.1:8080</li>
+     *      <li>dns.name.com:8080</li>
+     *      <li>[0fe:1::20]:8080</li>
+     *      <li>[::1]:8080</li>
+     * </ul>
+     * @param inet
+     * @return
+     */
+    public static String formatAddress(InetSocketAddress inet){
+        if(inet == null){
+            throw new NullPointerException();
+        }
+        StringBuilder result = new StringBuilder();
+        if(inet.isUnresolved()){
+            result.append(inet.getHostName());
+        }else{
+            result.append(formatPossibleIpv6Address(formatAddress(inet.getAddress())));
+        }
+        result.append(":").append(inet.getPort());
+        return result.toString();
+    }
+
+    /**
+     * Converts IPV6 int[] representation into valid IPV6 string literal. Sequence of '-1' values are converted into '::'.
+     * @param hexRepresentation
+     * @return
+     */
+    private static String formatAddress6(int[] hexRepresentation){
+        if(hexRepresentation == null){
+            throw new NullPointerException();
+        }
+        if(hexRepresentation.length != IPV6_LEN){
+            throw new IllegalArgumentException();
+        }
+        StringBuilder stringBuilder = new StringBuilder();
+        boolean inCompressedSection = false;
+        for(int i = 0;i<hexRepresentation.length;i++){
+            if(hexRepresentation[i] == -1){
+                if(!inCompressedSection){
+                    inCompressedSection = true;
+                    if(i == 0){
+                        stringBuilder.append("::");
+                    } else {
+                        stringBuilder.append(':');
+                    }
+                }
+            } else {
+                inCompressedSection = false;
+                stringBuilder.append(Integer.toHexString(hexRepresentation[i]));
+                if(i+1<hexRepresentation.length){
+                    stringBuilder.append(":");
+                }
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+    public static boolean isBindingToMulticastDressSupported() {
+        return can_bind_to_mcast_addr;
+    }
+
+    private static void compactLongestZeroSequence(int[] hexRepresentatoin){
+        int bestRunStart = -1;
+        int bestRunLen = -1;
+        boolean inRun = false;
+        int runStart = -1;
+        for(int i=0;i<hexRepresentatoin.length;i++){
+
+            if(hexRepresentatoin[i] == 0){
+                if(!inRun){
+                    runStart = i;
+                    inRun = true;
+                }
+            } else {
+                if(inRun){
+                    inRun = false;
+                    int runLen = i - runStart;
+                    if(bestRunLen < 0){
+                        bestRunStart = runStart;
+                        bestRunLen = runLen;
+                    } else {
+                        if(runLen > bestRunLen){
+                            bestRunStart = runStart;
+                            bestRunLen = runLen;
+                        }
+                    }
+                }
+            }
+        }
+        if(bestRunStart >=0){
+            Arrays.fill(hexRepresentatoin, bestRunStart, bestRunStart + bestRunLen, -1);
+        }
+    }
+
+    private static boolean checkForLinux() {
+        return checkForPresence("os.name", "linux");
+    }
+
+    private static boolean checkForHp() {
+        return checkForPresence("os.name", "hp");
+    }
+
+    private static boolean checkForSolaris() {
+        return checkForPresence("os.name", "sun");
+    }
+
+    private static boolean checkForWindows() {
+        return checkForPresence("os.name", "win");
+    }
+
+    public static boolean checkForMac() {
+        return checkForPresence("os.name", "mac");
+    }
+
+    private static boolean checkForPresence(final String key, final String value) {
+        final String tmp = System.getProperty(key, value);
+        try {
+            return tmp != null && tmp.trim().toLowerCase(Locale.ENGLISH).startsWith(value);
+        } catch (Throwable t) {
+            return false;
+        }
+    }
+
+    // No instantiation
+    private NetworkUtils() {
+
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationManagement.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationManagement.java
index 0e9234f..86b54fa 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationManagement.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationManagement.java
@@ -64,7 +64,7 @@ public class NodesRegistrationManagement {
     protected void sendRegistrationEvent(KeycloakDeployment deployment) {
         log.debug("Sending registration event right now");
 
-        String host = HostUtils.getIpAddress();
+        String host = HostUtils.getHostName();
         try {
             ServerRequest.invokeRegisterNode(deployment, host);
             NodeRegistrationContext regContext = new NodeRegistrationContext(Time.currentTime(), deployment);
@@ -84,7 +84,7 @@ public class NodesRegistrationManagement {
     protected boolean sendUnregistrationEvent(KeycloakDeployment deployment) {
         log.debug("Sending Unregistration event right now");
 
-        String host = HostUtils.getIpAddress();
+        String host = HostUtils.getHostName();
         try {
             ServerRequest.invokeUnregisterNode(deployment, host);
             log.debugf("Node '%s' successfully unregistered from Keycloak", host);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index 741edc7..eebac4f 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -103,7 +103,7 @@ public class ServerRequest {
         formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
         if (sessionId != null) {
             formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_STATE, sessionId));
-            formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_HOST, HostUtils.getIpAddress()));
+            formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_HOST, HostUtils.getHostName()));
         }
         HttpResponse response = null;
         HttpPost post = new HttpPost(codeUrl);