Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0a3341b9 |
No files matched your search
@@ -111,5 +111,9 @@ public class DemoUsers implements QAuthenticationModuleCustomizerInterface
|
||||
}
|
||||
}
|
||||
session.withValueForFrontend("user", new LinkedHashMap<>(Map.of("name", name, "email", userId + "@example.test")));
|
||||
if(EnterpriseFeatures.isEnabled())
|
||||
{
|
||||
EnterpriseSessions.record(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2026 Kingsrook, LLC
|
||||
* 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 https://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package com.kingsrook.qqq.all;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QSessionStoreHelper;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
|
||||
/**
|
||||
* Records mock sign-ins in the session store (qbit-session-store) for the
|
||||
* enterprise profile.
|
||||
*
|
||||
* Only the OAuth2 module writes to the session store; MOCK authentication
|
||||
* builds a fresh session on every request and stores nothing, so the
|
||||
* storedSession table stayed empty and "who is online" could not count
|
||||
* terminals. This is the smallest hook: the mock module's session customizer
|
||||
* calls {@link #record(QSession)}, which stores one row per session uuid
|
||||
* (user id, a lean copy of the session, expiresAt) the first time this JVM
|
||||
* sees it and touches it (sliding expiry) at most once a minute after that.
|
||||
*/
|
||||
final class EnterpriseSessions
|
||||
{
|
||||
static final Duration TOUCH_INTERVAL = Duration.ofMinutes(1);
|
||||
|
||||
private static final Map<String, Long> LAST_SEEN = new ConcurrentHashMap<>();
|
||||
|
||||
private EnterpriseSessions()
|
||||
{
|
||||
}
|
||||
|
||||
static void record(QSession session)
|
||||
{
|
||||
String uuid = session == null ? null : session.getUuid();
|
||||
if(uuid == null || !QSessionStoreHelper.isSessionStoreAvailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = LAST_SEEN.get(uuid);
|
||||
if(last != null && now - last < TOUCH_INTERVAL.toMillis())
|
||||
{
|
||||
return;
|
||||
}
|
||||
LAST_SEEN.put(uuid, now);
|
||||
if(last == null && QSessionStoreHelper.loadSession(uuid).isEmpty())
|
||||
{
|
||||
QSessionStoreHelper.storeSession(uuid, lean(session), QSessionStoreHelper.getDefaultTtl());
|
||||
}
|
||||
else
|
||||
{
|
||||
QSessionStoreHelper.touchSession(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
/** The identity only: the admin session's thousand permissions do not belong in a row. */
|
||||
static QSession lean(QSession session)
|
||||
{
|
||||
QSession lean = new QSession();
|
||||
lean.setUuid(session.getUuid());
|
||||
lean.setIdReference(session.getUuid());
|
||||
if(session.getUser() != null)
|
||||
{
|
||||
QUser user = new QUser();
|
||||
user.setIdReference(session.getUser().getIdReference());
|
||||
user.setFullName(session.getUser().getFullName());
|
||||
lean.setUser(user);
|
||||
}
|
||||
return (lean);
|
||||
}
|
||||
|
||||
/** Tests start from a clean slate. */
|
||||
static void forget()
|
||||
{
|
||||
LAST_SEEN.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2026 Kingsrook, LLC
|
||||
* 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 https://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package com.kingsrook.qqq.all;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QSessionStoreProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QSessionStoreRegistry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/** Mock sign-ins become one stored session per uuid, touched rather than re-stored. */
|
||||
class EnterpriseSessionsTest
|
||||
{
|
||||
private final RecordingStore store = new RecordingStore();
|
||||
|
||||
@BeforeEach
|
||||
void setUp()
|
||||
{
|
||||
EnterpriseSessions.forget();
|
||||
QSessionStoreRegistry.getInstance().clear();
|
||||
QSessionStoreRegistry.getInstance().register(store);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown()
|
||||
{
|
||||
QSessionStoreRegistry.getInstance().clear();
|
||||
EnterpriseSessions.forget();
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneRowPerSessionUuid()
|
||||
{
|
||||
EnterpriseSessions.record(session("aaaa", "core:admin"));
|
||||
EnterpriseSessions.record(session("aaaa", "core:admin"));
|
||||
EnterpriseSessions.record(session("bbbb", "core:admin"));
|
||||
|
||||
assertEquals(List.of("aaaa", "bbbb"), new ArrayList<>(store.sessions.keySet()));
|
||||
assertEquals("core:admin", store.sessions.get("bbbb").getUser().getIdReference());
|
||||
assertNull(store.sessions.get("aaaa").getPermissions(), "only the identity is stored");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aKnownSessionIsTouchedNotStoredAgain()
|
||||
{
|
||||
store.sessions.put("cccc", session("cccc", "core:admin"));
|
||||
EnterpriseSessions.record(session("cccc", "core:admin"));
|
||||
assertEquals(0, store.stores);
|
||||
assertEquals(1, store.touches);
|
||||
}
|
||||
|
||||
private static QSession session(String uuid, String userId)
|
||||
{
|
||||
QSession session = new QSession();
|
||||
session.setUuid(uuid);
|
||||
QUser user = new QUser();
|
||||
user.setIdReference(userId);
|
||||
session.setUser(user);
|
||||
session.withPermissions("customer.read", "customer.edit");
|
||||
return (session);
|
||||
}
|
||||
|
||||
/** A session store that keeps what it is given and counts calls. */
|
||||
private static class RecordingStore implements QSessionStoreProviderInterface
|
||||
{
|
||||
private final Map<String, QSession> sessions = new LinkedHashMap<>();
|
||||
private int stores;
|
||||
private int touches;
|
||||
|
||||
@Override
|
||||
public void store(String sessionUuid, QSession session, Duration ttl)
|
||||
{
|
||||
stores++;
|
||||
sessions.put(sessionUuid, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<QSession> load(String sessionUuid)
|
||||
{
|
||||
return (Optional.ofNullable(sessions.get(sessionUuid)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String sessionUuid)
|
||||
{
|
||||
sessions.remove(sessionUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touch(String sessionUuid)
|
||||
{
|
||||
touches++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDefaultTtl()
|
||||
{
|
||||
return (Duration.ofHours(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user