SSSD User Federation integration for quarkus distribution
Closes https://github.com/keycloak/keycloak/issues/16165
This commit is contained in:
parent
87905c186d
commit
025778fe9c
9 changed files with 296 additions and 134 deletions
|
@ -9,6 +9,12 @@
|
|||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<artifactId>keycloak-sssd-federation</artifactId>
|
||||
<name>Keycloak SSSD Federation</name>
|
||||
<description/>
|
||||
|
@ -28,6 +34,17 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Multi-Release>true</Multi-Release>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -69,4 +86,38 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk-16</id>
|
||||
<activation>
|
||||
<jdk>[16,)</jdk>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-java16</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<release>16</release>
|
||||
<buildDirectory>${project.build.directory}</buildDirectory>
|
||||
<compileSourceRoots>${project.basedir}/src/main/java16</compileSourceRoots>
|
||||
<outputDirectory>${project.build.directory}/classes/META-INF/versions/16</outputDirectory>
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.build.outputDirectory}</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -308,7 +308,7 @@ public final class TransportBuilder {
|
|||
if (provider == null) {
|
||||
throw new DBusException("No transport provider found for bustype " + config.getBusAddress().getBusType());
|
||||
} else {
|
||||
LOGGER.info("Using transport {} for address {}", provider.getTransportName(), config.getBusAddress());
|
||||
LOGGER.debug("Using transport {} for address {}", provider.getTransportName(), config.getBusAddress());
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.federation.sssd.impl.PAMAuthenticator;
|
|||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.UserStoragePrivateUtil;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
@ -111,8 +112,12 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
protected UserModel importUserToKeycloak(RealmModel realm, String username) {
|
||||
Sssd sssd = new Sssd(username);
|
||||
Sssd sssd = new Sssd(username, factory.getDbusConnection());
|
||||
User sssdUser = sssd.getUser();
|
||||
if (sssdUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debugf("Creating SSSD user: %s to local Keycloak storage", username);
|
||||
UserModel user = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, username);
|
||||
user.setEnabled(true);
|
||||
|
@ -158,8 +163,8 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
public boolean isValid(RealmModel realm, UserModel local) {
|
||||
User user = new Sssd(local.getUsername()).getUser();
|
||||
return user.equals(local);
|
||||
User user = new Sssd(local.getUsername(), factory.getDbusConnection()).getUser();
|
||||
return user != null && user.equals(local);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -191,12 +196,11 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
|
||||
@Override
|
||||
public void close() {
|
||||
Sssd.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
throw new IllegalStateException("You can't update your password as your account is read only.");
|
||||
throw new ReadOnlyException("You can't update your password as your account is read only.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
|
||||
package org.keycloak.federation.sssd;
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.federation.sssd.api.Sssd;
|
||||
import org.keycloak.federation.sssd.impl.AvailabilityChecker;
|
||||
import org.keycloak.federation.sssd.impl.PAMAuthenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -37,6 +40,7 @@ public class SSSDFederationProviderFactory implements UserStorageProviderFactory
|
|||
private static final String PROVIDER_NAME = "sssd";
|
||||
private static final Logger logger = Logger.getLogger(SSSDFederationProvider.class);
|
||||
|
||||
private volatile DBusConnection dbusConnection;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
|
@ -45,6 +49,7 @@ public class SSSDFederationProviderFactory implements UserStorageProviderFactory
|
|||
|
||||
@Override
|
||||
public SSSDFederationProvider create(KeycloakSession session, ComponentModel model) {
|
||||
lazyInit();
|
||||
return new SSSDFederationProvider(session, new UserStorageProviderModel(model), this);
|
||||
}
|
||||
|
||||
|
@ -60,15 +65,36 @@ public class SSSDFederationProviderFactory implements UserStorageProviderFactory
|
|||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
if (dbusConnection != null) {
|
||||
dbusConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected PAMAuthenticator createPAMAuthenticator(String username, String... factors) {
|
||||
return new PAMAuthenticator(username, factors);
|
||||
}
|
||||
|
||||
protected DBusConnection getDbusConnection() {
|
||||
return dbusConnection;
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (dbusConnection == null) {
|
||||
synchronized (this) {
|
||||
if (dbusConnection == null) {
|
||||
try {
|
||||
dbusConnection = DBusConnectionBuilder.forSystemBus().build();
|
||||
} catch(DBusException e) {
|
||||
// should not happen as it should be supported to get this point
|
||||
throw new IllegalStateException("Cannot create DBUS connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Sssd.isAvailable();
|
||||
return AvailabilityChecker.isAvailable();
|
||||
}
|
||||
}
|
|
@ -17,18 +17,16 @@
|
|||
|
||||
package org.keycloak.federation.sssd.api;
|
||||
|
||||
import cx.ath.matthew.LibraryLoader;
|
||||
import org.freedesktop.dbus.DBusConnection;
|
||||
import org.freedesktop.dbus.Variant;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.freedesktop.sssd.infopipe.InfoPipe;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.types.DBusListType;
|
||||
import org.freedesktop.dbus.types.Variant;
|
||||
import org.freedesktop.sssd.infopipe.InfoPipe;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
|
||||
|
@ -36,34 +34,20 @@ import java.util.Vector;
|
|||
*/
|
||||
public class Sssd {
|
||||
|
||||
private static DBusConnection dBusConnection;
|
||||
|
||||
public static void disconnect() {
|
||||
dBusConnection.disconnect();
|
||||
}
|
||||
|
||||
private String username;
|
||||
private final DBusConnection dBusConnection;
|
||||
private final String username;
|
||||
private static final Logger logger = Logger.getLogger(Sssd.class);
|
||||
|
||||
private Sssd() {
|
||||
}
|
||||
|
||||
public Sssd(String username) {
|
||||
public Sssd(String username, DBusConnection dbusConnection) throws SSSDException {
|
||||
this.username = username;
|
||||
try {
|
||||
if (LibraryLoader.load().succeed())
|
||||
dBusConnection = DBusConnection.getConnection(DBusConnection.SYSTEM);
|
||||
} catch (DBusException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.dBusConnection = dbusConnection;
|
||||
}
|
||||
|
||||
public static String getRawAttribute(Variant variant) {
|
||||
if (variant != null) {
|
||||
Vector value = (Vector) variant.getValue();
|
||||
if (value.size() >= 1) {
|
||||
return value.get(0).toString();
|
||||
if (variant != null && variant.getType() instanceof DBusListType) {
|
||||
List<?> value = (List) variant.getValue();
|
||||
if (!value.isEmpty()) {
|
||||
return value.iterator().next().toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -75,41 +59,19 @@ public class Sssd {
|
|||
InfoPipe infoPipe = dBusConnection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
userGroups = infoPipe.getUserGroups(username);
|
||||
} catch (Exception e) {
|
||||
throw new SSSDException("Failed to retrieve user's groups from SSSD. Check if SSSD service is active.");
|
||||
throw new SSSDException("Failed to retrieve user's groups from SSSD. Check if SSSD service is active.", e);
|
||||
}
|
||||
return userGroups;
|
||||
}
|
||||
|
||||
public static boolean isAvailable() {
|
||||
boolean sssdAvailable = false;
|
||||
try {
|
||||
if (LibraryLoader.load().succeed()) {
|
||||
DBusConnection connection = DBusConnection.getConnection(DBusConnection.SYSTEM);
|
||||
InfoPipe infoPipe = connection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
|
||||
if (infoPipe.ping("PING") == null || infoPipe.ping("PING").isEmpty()) {
|
||||
logger.debugv("SSSD is not available in your system. Federation provider will be disabled.");
|
||||
} else {
|
||||
sssdAvailable = true;
|
||||
}
|
||||
} else {
|
||||
logger.debugv("The RPM libunix-dbus-java is not installed. SSSD Federation provider will be disabled.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debugv("SSSD is not available in your system. Federation provider will be disabled.", e);
|
||||
}
|
||||
return sssdAvailable;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
|
||||
String[] attr = {"mail", "givenname", "sn", "telephoneNumber"};
|
||||
User user = null;
|
||||
try {
|
||||
InfoPipe infoPipe = dBusConnection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
user = new User(infoPipe.getUserAttributes(username, Arrays.asList(attr)));
|
||||
} catch (Exception e) {
|
||||
throw new SSSDException("Failed to retrieve user's attributes. Check if SSSD service is active.");
|
||||
logger.debugf(e, "Failed to retrieve attributes for user '%s'. Check if SSSD service is active.", username);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2023 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.federation.sssd.impl;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* <p>Class to detect if SSSD is available in the system. As keycloak uses
|
||||
* the native java implementation (jdk >= 16 needed) the default implementation
|
||||
* for previous versions always returns false.</p>
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AvailabilityChecker {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AvailabilityChecker.class);
|
||||
|
||||
/**
|
||||
* Returns if the SSSD is available in the system.
|
||||
* @return true if SSSD is available, null otherwise
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
logger.debug("SSSD is not available for this version of java (jdk >= 16 needed). Federation provider will be disabled.");
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2023 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.federation.sssd.impl;
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
|
||||
import org.freedesktop.sssd.infopipe.InfoPipe;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* <p>Class to detect if SSSD is available in the system. Working
|
||||
* version for the native java transport.</p>
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AvailabilityChecker {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AvailabilityChecker.class);
|
||||
|
||||
/**
|
||||
* Returns if the SSSD is available in the system.
|
||||
* @return true if SSSD is available, null otherwise
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
boolean sssdAvailable = false;
|
||||
try (DBusConnection connection = DBusConnectionBuilder.forSystemBus().build()) {
|
||||
InfoPipe infoPipe = connection.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
|
||||
|
||||
if (infoPipe.ping("PING") == null || infoPipe.ping("PING").isEmpty()) {
|
||||
logger.debug("SSSD is not available in your system. Federation provider will be disabled.");
|
||||
} else {
|
||||
sssdAvailable = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("SSSD is not available in your system. Federation provider will be disabled.", e);
|
||||
}
|
||||
return sssdAvailable;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import { Link } from "react-router-dom";
|
|||
import { adminClient } from "../admin-client";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { toUserFederationLdap } from "../user-federation/routes/UserFederationLdap";
|
||||
import { toCustomUserFederation } from "../user-federation/routes/CustomUserFederation";
|
||||
import { useFetch } from "../utils/useFetch";
|
||||
|
||||
type FederatedUserLinkProps = {
|
||||
|
@ -42,8 +42,9 @@ export const FederatedUserLink = ({ user }: FederatedUserLinkProps) => {
|
|||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={toUserFederationLdap({
|
||||
to={toCustomUserFederation({
|
||||
id: component.id!,
|
||||
providerId: component.providerId!,
|
||||
realm,
|
||||
})}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package org.keycloak.testsuite.sssd;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.configuration.ConfigurationException;
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.Assert;
|
||||
|
@ -16,27 +19,49 @@ import org.junit.Before;
|
|||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public class SSSDTest extends AbstractKeycloakTest {
|
||||
/**
|
||||
* <p>The class needs a SSSD working environment with a set of users created.
|
||||
* The users to test are provided by the <em>sssd.properties</em> properties
|
||||
* file. Currently the users are the following:</p>
|
||||
*
|
||||
* <pre>
|
||||
* kinit admin
|
||||
* ipa group-add --desc='test group' testgroup
|
||||
* ipa user-add emily --first=Emily --last=Jones --email=emily@jones.com --password (emily123)
|
||||
* ipa group-add-member testgroup --users=emily
|
||||
* ipa user-add bart --first=bart --last=bart --email= --password (bart123)
|
||||
* ipa user-add david --first=david --last=david --password (david123)
|
||||
* ipa user-disable david
|
||||
* </pre>
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class SSSDTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static final Logger log = Logger.getLogger(SSSDTest.class);
|
||||
|
||||
|
@ -52,10 +77,10 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
private static PropertiesConfiguration sssdConfig;
|
||||
|
||||
@Page
|
||||
protected LoginPage accountLoginPage;
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected AccountUpdateProfilePage profilePage;
|
||||
protected AppPage appPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
@ -63,13 +88,7 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
private String SSSDFederationID;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = new RealmRepresentation();
|
||||
|
||||
realm.setRealm(REALM_NAME);
|
||||
realm.setEnabled(true);
|
||||
|
||||
testRealms.add(realm);
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
|
@ -93,9 +112,28 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
userFederation.setProviderType(UserStorageProvider.class.getName());
|
||||
userFederation.setProviderId(PROVIDER_NAME);
|
||||
|
||||
Response response = adminClient.realm(REALM_NAME).components().add(userFederation);
|
||||
try (Response response = adminClient.realm(REALM_NAME).components().add(userFederation)) {
|
||||
SSSDFederationID = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void testLoginFailure(String username, String password) {
|
||||
loginPage.open();
|
||||
loginPage.login(username, password);
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getInputError());
|
||||
events.expect(EventType.LOGIN_ERROR).user(Matchers.any(String.class)).error(Errors.INVALID_USER_CREDENTIALS).assertEvent();
|
||||
}
|
||||
|
||||
private void testLoginSuccess(String username) {
|
||||
loginPage.open();
|
||||
loginPage.login(username, getPassword(username));
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
EventRepresentation loginEvent = events.expectLogin().user(Matchers.any(String.class))
|
||||
.detail(Details.USERNAME, username).assertEvent();
|
||||
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
|
||||
appPage.logout(tokenResponse.getIdToken());
|
||||
events.expectLogout(loginEvent.getSessionId()).user(loginEvent.getUserId()).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,10 +141,7 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
String username = getUsername();
|
||||
log.debug("Testing invalid password for user " + username);
|
||||
|
||||
profilePage.open();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, "invalid-password");
|
||||
assertThat(accountLoginPage.getInputError(), is("Invalid username or password."));
|
||||
testLoginFailure(username, "invalid-password");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,11 +150,7 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
Assume.assumeTrue("Ignoring test no disabled user configured", username != null);
|
||||
log.debug("Testing disabled user " + username);
|
||||
|
||||
profilePage.open();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
|
||||
assertThat(accountLoginPage.getInputError(), is("Invalid username or password."));
|
||||
testLoginFailure(username, getPassword(username));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -127,11 +158,7 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
String username = getUser(ADMIN_USER);
|
||||
Assume.assumeTrue("Ignoring test no admin user configured", username != null);
|
||||
log.debug("Testing password for user " + username);
|
||||
|
||||
profilePage.open();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
assertThat(profilePage.isCurrent(), is(true));
|
||||
testLoginSuccess(username);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -139,34 +166,23 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
log.debug("Testing correct password");
|
||||
|
||||
for (String username : getUsernames()) {
|
||||
profilePage.open();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
assertThat(profilePage.isCurrent(), is(true));
|
||||
testLoginSuccess(username);
|
||||
verifyUserGroups(username, getGroups(username));
|
||||
profilePage.logout();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingUserWithNoEmailLogIn() {
|
||||
log.debug("Testing correct password, but no e-mail provided");
|
||||
String username = getUser(NO_EMAIL_USER);
|
||||
profilePage.open();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
assertThat(profilePage.isCurrent(), is(true));
|
||||
testLoginSuccess(getUser(NO_EMAIL_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteSSSDFederationProvider() {
|
||||
log.debug("Testing correct password");
|
||||
|
||||
profilePage.open();
|
||||
String username = getUsername();
|
||||
assertThat("Browser should be on login page now", driver.getTitle(), is("Sign in to " + REALM_NAME));
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
assertThat(profilePage.isCurrent(), is(true));
|
||||
testLoginSuccess(username);
|
||||
verifyUserGroups(username, getGroups(username));
|
||||
|
||||
int componentsListSize = adminClient.realm(REALM_NAME).components().query().size();
|
||||
|
@ -179,27 +195,39 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
public void changeReadOnlyProfile() {
|
||||
|
||||
String username = getUsername();
|
||||
profilePage.open();
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
|
||||
assertThat(profilePage.getUsername(), is(username));
|
||||
assertThat(sssdConfig.getProperty("user." + username + ".firstname"), is(profilePage.getFirstName()));
|
||||
assertThat(sssdConfig.getProperty("user." + username + ".lastname"), is(profilePage.getLastName()));
|
||||
assertThat(sssdConfig.getProperty("user." + username + ".mail"), is(profilePage.getEmail()));
|
||||
testLoginSuccess(username);
|
||||
|
||||
profilePage.updateProfile("New first", "New last", "new@email.com");
|
||||
RealmResource realm = adminClient.realm(REALM_NAME);
|
||||
List<UserRepresentation> users = realm.users().search(username, true);
|
||||
Assert.assertEquals(1, users.size());
|
||||
UserRepresentation user = users.iterator().next();
|
||||
user.setLastName("changed");
|
||||
|
||||
assertThat(profilePage.getError(), is("You can't update your account as it is read-only."));
|
||||
BadRequestException e = Assert.assertThrows(BadRequestException.class,
|
||||
() -> realm.users().get(users.iterator().next().getId()).update(user));
|
||||
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
|
||||
Assert.assertEquals("User is read only!", error.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeReadOnlyPassword() {
|
||||
String username = getUsername();
|
||||
accountLoginPage.open();
|
||||
accountLoginPage.login(username, getPassword(username));
|
||||
|
||||
Assert.assertFalse(AccountHelper.updatePassword(adminClient.realm(REALM_NAME), getPassword(username), "new-password"));
|
||||
assertThat(profilePage.getError(), is("You can't update your password as your account is read only."));
|
||||
testLoginSuccess(username);
|
||||
|
||||
RealmResource realm = adminClient.realm(REALM_NAME);
|
||||
List<UserRepresentation> users = realm.users().search(username, true);
|
||||
Assert.assertEquals(1, users.size());
|
||||
CredentialRepresentation newPassword = new CredentialRepresentation();
|
||||
newPassword.setType(CredentialRepresentation.PASSWORD);
|
||||
newPassword.setValue("new-password-123!");
|
||||
newPassword.setTemporary(false);
|
||||
|
||||
BadRequestException e = Assert.assertThrows(BadRequestException.class,
|
||||
() -> realm.users().get(users.iterator().next().getId()).resetPassword(newPassword));
|
||||
OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
|
||||
Assert.assertEquals("Can't reset password as account is read only", error.getError());
|
||||
}
|
||||
|
||||
private void verifyUserGroups(String username, List<String> groups) {
|
||||
|
@ -207,11 +235,8 @@ public class SSSDTest extends AbstractKeycloakTest {
|
|||
assertThat("There must be at least one user", users.size(), greaterThan(0));
|
||||
assertThat("Exactly our test user", users.get(0).getUsername(), is(username));
|
||||
List<GroupRepresentation> assignedGroups = adminClient.realm(REALM_NAME).users().get(users.get(0).getId()).groups();
|
||||
assertThat("User must have exactly " + groups.size() + " groups", assignedGroups.size(), is(groups.size()));
|
||||
|
||||
for (GroupRepresentation group : assignedGroups) {
|
||||
assertThat(groups.contains(group.getName()), is(true));
|
||||
}
|
||||
List<String> assignedGroupNames = assignedGroups.stream().map(GroupRepresentation::getName).collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(assignedGroupNames, Matchers.hasItems(groups.toArray(new String[0])));
|
||||
}
|
||||
|
||||
private String getUsername() {
|
||||
|
|
Loading…
Reference in a new issue