KEYCLOAK-19297 Use real 'external' client object id to store AuthenticatedClientSession in UserSession object, so that the client session can be looked by the client object id in further requests.

This commit is contained in:
Luca Graf 2021-11-09 11:30:37 +01:00 committed by Hynek Mlnařík
parent caf37b1f70
commit febb447919
4 changed files with 154 additions and 3 deletions

View file

@ -413,7 +413,12 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
return false;
}
userSession.getAuthenticatedClientSessions().put(clientSessionEntity.getClientId(), clientSessAdapter);
String clientId = clientSessionEntity.getClientId();
if (isExternalClient(clientSessionEntity)) {
clientId = getExternalClientId(clientSessionEntity);
}
userSession.getAuthenticatedClientSessions().put(clientId, clientSessAdapter);
return true;
}
@ -439,8 +444,8 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
String clientId = entity.getClientId();
if (!entity.getExternalClientId().equals("local")) {
clientId = new StorageId(entity.getClientStorageProvider(), entity.getExternalClientId()).getId();
if (isExternalClient(entity)) {
clientId = getExternalClientId(entity);
}
ClientModel client = realm.getClientById(clientId);
@ -497,4 +502,12 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
private boolean offlineFromString(String offlineStr) {
return "1".equals(offlineStr);
}
private boolean isExternalClient(PersistentClientSessionEntity entity) {
return !entity.getExternalClientId().equals(PersistentClientSessionEntity.LOCAL);
}
private String getExternalClientId(PersistentClientSessionEntity entity) {
return new StorageId(entity.getClientStorageProvider(), entity.getExternalClientId()).getId();
}
}

View file

@ -187,6 +187,13 @@
</properties>
</profile>
<profile>
<id>jpa+infinispan+client-storage</id>
<properties>
<keycloak.model.parameters>Jpa,Infinispan,HardcodedClientStorage</keycloak.model.parameters>
</properties>
</profile>
<profile>
<id>jpa+cross-dc-infinispan</id>
<properties>

View file

@ -0,0 +1,64 @@
/*
* Copyright 2020 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.testsuite.model.parameters;
import com.google.common.collect.ImmutableSet;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.storage.client.ClientStorageProviderSpi;
import org.keycloak.testsuite.federation.HardcodedClientStorageProviderFactory;
import org.keycloak.testsuite.model.Config;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class HardcodedClientStorage extends KeycloakModelParameters {
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(ClientStorageProviderSpi.class)
.build();
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(HardcodedClientStorageProviderFactory.class)
.build();
private final AtomicInteger counter = new AtomicInteger();
@Override
public void updateConfig(Config cf) {
cf.spi("client-storage").defaultProvider(HardcodedClientStorageProviderFactory.PROVIDER_ID);
}
@Override
public <T> Stream<T> getParameters(Class<T> clazz) {
if (ClientStorageProviderModel.class.isAssignableFrom(clazz)) {
ClientStorageProviderModel clientStorage = new ClientStorageProviderModel();
clientStorage.setName(HardcodedClientStorageProviderFactory.PROVIDER_ID + ":" + counter.getAndIncrement());
clientStorage.setProviderId(HardcodedClientStorageProviderFactory.PROVIDER_ID);
return Stream.of((T) clientStorage);
} else {
return super.getParameters(clazz);
}
}
public HardcodedClientStorage() {
super(ALLOWED_SPIS, ALLOWED_FACTORIES);
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.model.session;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
@ -33,6 +34,7 @@ import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.ResetTimeOffsetEvent;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
@ -53,6 +55,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
import org.keycloak.models.Constants;
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
import org.hamcrest.Matchers;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.testsuite.federation.HardcodedClientStorageProviderFactory;
import org.keycloak.testsuite.model.KeycloakModelTest;
import org.keycloak.testsuite.model.RequireProvider;
import java.util.LinkedList;
@ -508,6 +513,68 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
});
}
@Test
@RequireProvider(ClientStorageProvider.class)
public void testPersistenceWithLoadWithExternalClientStorage() {
try {
inComittedTransaction(session -> {
setupClientStorageComponents(session, session.realms().getRealm(realmId));
});
int started = Time.currentTime();
UserSessionModel origSession = inComittedTransaction(session -> {
// Create session in infinispan
RealmModel realm = session.realms().getRealm(realmId);
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
createClientSession(session, realmId, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state");
createClientSession(session, realmId, realm.getClientByClientId("external-storage-client"), userSession, "http://redirect", "state");
return userSession;
});
inComittedTransaction(session -> {
// Persist created userSession and clientSessions as offline
persistUserSession(session, origSession, true);
});
inComittedTransaction(session -> {
// Assert offline session
RealmModel realm = session.realms().getRealm(realmId);
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 1, 1, 1);
assertSessions(loadedSessions, new String[]{origSession.getId()});
assertSessionLoaded(loadedSessions, origSession.getId(), session.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "external-storage-client");
});
} finally {
inComittedTransaction(session -> {
cleanClientStorageComponents(session, session.realms().getRealm(realmId));
});
}
}
private void setupClientStorageComponents(KeycloakSession s, RealmModel realm) {
getParameters(ClientStorageProviderModel.class).forEach(cm -> {
cm.put(HardcodedClientStorageProviderFactory.CLIENT_ID, "external-storage-client");
cm.put(HardcodedClientStorageProviderFactory.DELAYED_SEARCH, Boolean.toString(false));
realm.addComponentModel(cm);
});
// Required by HardcodedClientStorageProvider
s.roles().addRealmRole(realm, OAuth2Constants.OFFLINE_ACCESS);
s.clientScopes().addClientScope(realm, OAuth2Constants.OFFLINE_ACCESS);
s.clientScopes().addClientScope(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
s.clientScopes().addClientScope(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
}
private void cleanClientStorageComponents(KeycloakSession s, RealmModel realm) {
s.roles().removeRoles(realm);
s.clientScopes().removeClientScopes(realm);
realm.removeComponents(realm.getId());
}
protected static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, String realmId, ClientModel client, UserSessionModel userSession, String redirect, String state) {
RealmModel realm = session.realms().getRealm(realmId);
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);