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:
parent
caf37b1f70
commit
febb447919
4 changed files with 154 additions and 3 deletions
|
@ -413,7 +413,12 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,8 +444,8 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
|
|
||||||
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
|
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
|
||||||
String clientId = entity.getClientId();
|
String clientId = entity.getClientId();
|
||||||
if (!entity.getExternalClientId().equals("local")) {
|
if (isExternalClient(entity)) {
|
||||||
clientId = new StorageId(entity.getClientStorageProvider(), entity.getExternalClientId()).getId();
|
clientId = getExternalClientId(entity);
|
||||||
}
|
}
|
||||||
ClientModel client = realm.getClientById(clientId);
|
ClientModel client = realm.getClientById(clientId);
|
||||||
|
|
||||||
|
@ -497,4 +502,12 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
private boolean offlineFromString(String offlineStr) {
|
private boolean offlineFromString(String offlineStr) {
|
||||||
return "1".equals(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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,6 +187,13 @@
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>jpa+infinispan+client-storage</id>
|
||||||
|
<properties>
|
||||||
|
<keycloak.model.parameters>Jpa,Infinispan,HardcodedClientStorage</keycloak.model.parameters>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>jpa+cross-dc-infinispan</id>
|
<id>jpa+cross-dc-infinispan</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.model.session;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -33,6 +34,7 @@ import org.keycloak.models.UserSessionProvider;
|
||||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
import org.keycloak.services.managers.ClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
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.Constants;
|
||||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||||
import org.hamcrest.Matchers;
|
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.KeycloakModelTest;
|
||||||
import org.keycloak.testsuite.model.RequireProvider;
|
import org.keycloak.testsuite.model.RequireProvider;
|
||||||
import java.util.LinkedList;
|
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) {
|
protected static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, String realmId, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
||||||
RealmModel realm = session.realms().getRealm(realmId);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
|
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
|
||||||
|
|
Loading…
Reference in a new issue