From 36bb94afb85473842a086b24fcfa983600a0139d Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 8 Sep 2016 07:36:00 +0200 Subject: [PATCH 1/2] Environment dependent provider --- .../EnvironmentDependentProviderFactory.java | 33 +++++++++++++++++++ .../DefaultKeycloakSessionFactory.java | 15 +++++++-- .../PlainTextPasswordProviderFactory.java | 11 ++++++- 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java diff --git a/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java new file mode 100644 index 0000000000..b4e993a2c9 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.provider; + +/** + * Providers that are only supported in some environments can implement this interface to be able to determine if they + * should be available or not. + * + * @author Stian Thorgersen + */ +public interface EnvironmentDependentProviderFactory { + + /** + * @return true if the provider is supported and should be available, false otherwise + */ + boolean isSupported(); + +} diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 45bef3c3ea..a1521bd659 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -20,6 +20,7 @@ import org.keycloak.Config; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderEventListener; @@ -193,8 +194,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } Config.Scope scope = Config.scope(spi.getName(), provider); - if (scope.getBoolean("enabled", true)) { - + if (isEnabled(factory, scope)) { factory.init(scope); if (spi.isInternal() && !isInternal(factory)) { @@ -208,7 +208,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } else { for (ProviderFactory factory : pm.load(spi)) { Config.Scope scope = Config.scope(spi.getName(), factory.getId()); - if (scope.getBoolean("enabled", true)) { + if (isEnabled(factory, scope)) { factory.init(scope); if (spi.isInternal() && !isInternal(factory)) { @@ -223,7 +223,16 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } } return factoryMap; + } + private boolean isEnabled(ProviderFactory factory, Config.Scope scope) { + if (!scope.getBoolean("enabled", true)) { + return false; + } + if (factory instanceof EnvironmentDependentProviderFactory) { + return ((EnvironmentDependentProviderFactory) factory).isSupported(); + } + return true; } protected void loadSPIs(ProviderManager pm, List spiList) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java index cdb324ca5c..dd281d17f8 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java @@ -21,12 +21,15 @@ import org.keycloak.hash.PasswordHashProvider; import org.keycloak.hash.PasswordHashProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.EnvironmentDependentProviderFactory; + +import java.io.File; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory { +public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory, EnvironmentDependentProviderFactory { @Override public PasswordHashProvider create(KeycloakSession session) { return new PlainTextPasswordProvider(); @@ -51,4 +54,10 @@ public class PlainTextPasswordProviderFactory implements PasswordHashProviderFac public String getId() { return "text"; } + + // TODO REMOVE THIS + @Override + public boolean isSupported() { + return !new File("/tmp/disable-text-hash").exists(); + } } From 11245701d27bf19ef20f948359d92510e5aedf7d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 8 Sep 2016 07:36:33 -0300 Subject: [PATCH 2/2] Check if SSSD is available via DBUS --- .../java/cx/ath/matthew/LibraryLoader.java | 8 ++++- .../src/main/java/org/freedesktop/DBus.java | 30 ++++++++++-------- .../freedesktop/sssd/infopipe/InfoPipe.java | 4 ++- .../sssd/SSSDFederationProviderFactory.java | 10 ++++-- .../keycloak/federation/sssd/api/Sssd.java | 31 ++++++++++++++++--- .../DefaultKeycloakSessionFactory.java | 1 + .../testsuite/admin/UserFederationTest.java | 2 +- .../PlainTextPasswordProviderFactory.java | 11 +------ 8 files changed, 64 insertions(+), 33 deletions(-) diff --git a/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java index ffdf02dd6e..095d214467 100644 --- a/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java +++ b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java @@ -31,7 +31,7 @@ public class LibraryLoader { private static final String VERSION = "0.0.8"; private static boolean loadSucceeded; - public static void load() { + public static LibraryLoader load() { for (String path : PATHS) { try { System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION)); @@ -45,5 +45,11 @@ public class LibraryLoader { if (!loadSucceeded) LOGGER.log(Level.WARNING, "libunix_dbus_java not found\n" + "Please, make sure you have the package libunix-dbus-java installed."); + + return new LibraryLoader(); + } + + public boolean succeed() { + return loadSucceeded; } } diff --git a/federation/sssd/src/main/java/org/freedesktop/DBus.java b/federation/sssd/src/main/java/org/freedesktop/DBus.java index 1aa180a4ab..b7a16877ae 100644 --- a/federation/sssd/src/main/java/org/freedesktop/DBus.java +++ b/federation/sssd/src/main/java/org/freedesktop/DBus.java @@ -30,18 +30,22 @@ import java.util.List; import java.util.Map; public interface DBus extends DBusInterface { - public static final int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; - public static final int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; - public static final int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; - public static final int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; - public static final int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; - public static final int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; - public static final int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; - public static final int DBUS_RELEASE_NAME_REPLY_RELEASED = 1; - public static final int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; - public static final int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; - public static final int DBUS_START_REPLY_SUCCESS = 1; - public static final int DBUS_START_REPLY_ALREADY_RUNNING = 2; + + String BUSNAME = "org.freedesktop.DBus"; + String OBJECTPATH = "/org/freedesktop/DBus"; + + int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; + int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; + int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; + int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; + int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; + int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; + int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; + int DBUS_RELEASEME_REPLY_RELEASED = 1; + int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; + int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; + int DBUS_START_REPLY_SUCCESS = 1; + int DBUS_START_REPLY_ALREADY_RUNNING = 2; /** * All DBus Applications should respond to the Ping method on this interface @@ -527,4 +531,4 @@ public interface DBus extends DBusInterface { } } } -} +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java index 8791170caf..6152d26a25 100644 --- a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java +++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java @@ -32,6 +32,8 @@ import java.util.Map; public interface InfoPipe extends DBusInterface { String OBJECTPATH = "/org/freedesktop/sssd/infopipe"; + String BUSNAME = "org.freedesktop.sssd.infopipe"; + @DBusMemberName("GetUserAttr") Map getUserAttributes(String user, List attr); @@ -39,4 +41,4 @@ public interface InfoPipe extends DBusInterface { @DBusMemberName("GetUserGroups") List getUserGroups(String user); -} +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java index 6a287a7ad8..3140e9e48a 100755 --- a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java @@ -19,6 +19,7 @@ package org.keycloak.federation.sssd; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.federation.sssd.api.Sssd; import org.keycloak.federation.sssd.impl.PAMAuthenticator; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -26,6 +27,7 @@ import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationSyncResult; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import java.util.Date; import java.util.HashSet; @@ -35,7 +37,7 @@ import java.util.Set; * @author Bruno Oliveira * @version $Revision: 1 $ */ -public class SSSDFederationProviderFactory implements UserFederationProviderFactory { +public class SSSDFederationProviderFactory implements UserFederationProviderFactory, EnvironmentDependentProviderFactory { private static final String PROVIDER_NAME = "sssd"; private static final Logger logger = Logger.getLogger(SSSDFederationProvider.class); @@ -99,4 +101,8 @@ public class SSSDFederationProviderFactory implements UserFederationProviderFact return new PAMAuthenticator(username, factors); } -} + @Override + public boolean isSupported() { + return Sssd.isAvailable(); + } +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java index a5bb57a308..fd9dc6752e 100644 --- a/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java @@ -17,6 +17,8 @@ package org.keycloak.federation.sssd.api; +import cx.ath.matthew.LibraryLoader; +import org.freedesktop.DBus; import org.freedesktop.dbus.DBusConnection; import org.freedesktop.dbus.Variant; import org.freedesktop.dbus.exceptions.DBusException; @@ -35,8 +37,6 @@ import java.util.Vector; */ public class Sssd { - public static final String BUSNAME = "org.freedesktop.sssd.infopipe"; - public static User user() { return SingletonHolder.USER_OBJECT; } @@ -45,6 +45,7 @@ public class Sssd { return SingletonHolder.INFOPIPE_OBJECT; } + public static void disconnect() { SingletonHolder.DBUS_CONNECTION.disconnect(); } @@ -67,10 +68,10 @@ public class Sssd { static { try { DBUS_CONNECTION = DBusConnection.getConnection(DBusConnection.SYSTEM); - INFOPIPE_OBJECT = DBUS_CONNECTION.getRemoteObject(BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class); - USER_OBJECT = DBUS_CONNECTION.getRemoteObject(BUSNAME, User.OBJECTPATH, User.class); + INFOPIPE_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class); + USER_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, User.OBJECTPATH, User.class); } catch (DBusException e) { - e.printStackTrace(); + logger.error("Failed to obtain D-Bus connection", e); } } } @@ -108,4 +109,24 @@ public class Sssd { } return userGroups; } + + public static boolean isAvailable(){ + boolean sssdAvailable = false; + try { + if (LibraryLoader.load().succeed()) { + DBusConnection connection = DBusConnection.getConnection(DBusConnection.SYSTEM); + DBus dbus = connection.getRemoteObject(DBus.BUSNAME, DBus.OBJECTPATH, DBus.class); + sssdAvailable = Arrays.asList(dbus.ListNames()).contains(InfoPipe.BUSNAME); + if (!sssdAvailable) { + logger.debugv("SSSD is not available in your system. Federation provider will be disabled."); + } else { + sssdAvailable = true; + } + connection.disconnect(); + } + } catch (DBusException e) { + logger.error("Failed to check the status of SSSD", e); + } + return sssdAvailable; + } } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index a1521bd659..3965f714d8 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -205,6 +205,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider); } + } else { for (ProviderFactory factory : pm.load(spi)) { Config.Scope scope = Config.scope(spi.getName(), factory.getId()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java index 7a939c3518..8cef8a1bbc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java @@ -51,7 +51,7 @@ public class UserFederationTest extends AbstractAdminTest { @Test public void testProviderFactories() { List providerFactories = userFederation().getProviderFactories(); - Assert.assertNames(providerFactories, "ldap", "kerberos", "dummy", "dummy-configurable", "sssd"); + Assert.assertNames(providerFactories, "ldap", "kerberos", "dummy", "dummy-configurable"); // Builtin provider without properties UserFederationProviderFactoryRepresentation ldapProvider = userFederation().getProviderFactory("ldap"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java index dd281d17f8..cdb324ca5c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java @@ -21,15 +21,12 @@ import org.keycloak.hash.PasswordHashProvider; import org.keycloak.hash.PasswordHashProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -import java.io.File; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory, EnvironmentDependentProviderFactory { +public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory { @Override public PasswordHashProvider create(KeycloakSession session) { return new PlainTextPasswordProvider(); @@ -54,10 +51,4 @@ public class PlainTextPasswordProviderFactory implements PasswordHashProviderFac public String getId() { return "text"; } - - // TODO REMOVE THIS - @Override - public boolean isSupported() { - return !new File("/tmp/disable-text-hash").exists(); - } }