diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/security/SimplePrincipalWithDelegation.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/security/SimplePrincipalWithDelegation.java index 90bdcbdb56a..7d66363534e 100644 --- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/security/SimplePrincipalWithDelegation.java +++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/security/SimplePrincipalWithDelegation.java @@ -10,6 +10,7 @@ package org.eclipse.scout.rt.platform.security; import java.security.Principal; +import java.util.Objects; import org.ietf.jgss.GSSCredential; @@ -34,4 +35,17 @@ public String getName() { public GSSCredential getDelegatedCred() { return m_delegatedCred; } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SimplePrincipalWithDelegation that)) { + return false; + } + return Objects.equals(m_name, that.m_name) && Objects.equals(m_delegatedCred, that.m_delegatedCred); + } + + @Override + public int hashCode() { + return Objects.hash(m_name, m_delegatedCred); + } } diff --git a/org.eclipse.scout.rt.server.commons/src/main/java/org/eclipse/scout/rt/server/commons/authentication/IAccessController.java b/org.eclipse.scout.rt.server.commons/src/main/java/org/eclipse/scout/rt/server/commons/authentication/IAccessController.java index ecf36f6069b..53bcca6c60a 100644 --- a/org.eclipse.scout.rt.server.commons/src/main/java/org/eclipse/scout/rt/server/commons/authentication/IAccessController.java +++ b/org.eclipse.scout.rt.server.commons/src/main/java/org/eclipse/scout/rt/server/commons/authentication/IAccessController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 BSI Business Systems Integration AG + * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -38,13 +38,7 @@ */ @Bean public interface IAccessController { - - /** - * Allows to inform the {@link org.eclipse.scout.rt.ui.html.IUiSession} that a new Subject is created by an - * {@link IAccessController} that should be updated on the {@link org.eclipse.scout.rt.client.IClientSession} - */ - String UPDATED_SUBJECT = "scout.authentication.updatedSubject"; - + /** * Invoke to authenticate the given {@link HttpServletRequest}. *

diff --git a/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionInitAndDisposeTest.java b/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionInitAndDisposeTest.java index cd234d5300a..cc23dfe08fa 100644 --- a/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionInitAndDisposeTest.java +++ b/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionInitAndDisposeTest.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.stream.Collectors; +import javax.security.auth.Subject; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @@ -36,7 +38,9 @@ import org.eclipse.scout.rt.platform.IBeanInstanceProducer; import org.eclipse.scout.rt.platform.IgnoreBean; import org.eclipse.scout.rt.platform.classid.ClassId; +import org.eclipse.scout.rt.platform.context.RunContext; import org.eclipse.scout.rt.platform.job.Jobs; +import org.eclipse.scout.rt.platform.util.ObjectUtility; import org.eclipse.scout.rt.server.commons.BufferedServletOutputStream; import org.eclipse.scout.rt.testing.platform.BeanTestingHelper; import org.eclipse.scout.rt.testing.platform.job.JobTestUtil; @@ -50,6 +54,7 @@ import org.eclipse.scout.rt.ui.html.json.testing.TestEnvironmentUiSession; import org.json.JSONObject; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -134,7 +139,8 @@ public void testStartupThenModelStop() throws IOException { "UiSession.getOrCreateClientSession", "ClientSession.execLoadSession", "Desktop.execOpened", - "Desktop.execGuiAttached"), + "Desktop.execGuiAttached", + "UiSession.verifySubject"), m_protocol); m_protocol.clear(); @@ -190,11 +196,12 @@ public void testStartupThenBrowserTabClose() throws IOException { "UiSession.getOrCreateClientSession", "ClientSession.execLoadSession", "Desktop.execOpened", - "Desktop.execGuiAttached"), + "Desktop.execGuiAttached", + "UiSession.verifySubject"), m_protocol); m_protocol.clear(); - //brower tab closed -> json unload + //browser tab closed -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionId, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); @@ -248,11 +255,12 @@ public void testStartupThenBrowserTabReload() throws IOException { "UiSession.getOrCreateClientSession", "ClientSession.execLoadSession", "Desktop.execOpened", - "Desktop.execGuiAttached"), + "Desktop.execGuiAttached", + "UiSession.verifySubject"), m_protocol); m_protocol.clear(); - //brower tab reload -> json unload + //browser tab reload -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionId, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); @@ -288,13 +296,14 @@ public void testStartupThenBrowserTabReload() throws IOException { Arrays.asList( "UiSession.dispose", "UiSession.init", - "UiSession.getOrCreateClientSession"), + "UiSession.getOrCreateClientSession", + "UiSession.verifySubject"), m_protocol.stream().filter(s -> s.startsWith("UiSession.")).collect(Collectors.toList())); m_protocol.clear(); assertEquals(1, store.countClientSessions()); assertEquals(1, store.countUiSessions()); - //brower tab closed -> json unload + //browser tab closed -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionId, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); @@ -345,11 +354,12 @@ public void testStartupThenBrowserTabDuplicate() throws IOException { "UiSession.getOrCreateClientSession", "ClientSession.execLoadSession", "Desktop.execOpened", - "Desktop.execGuiAttached"), + "Desktop.execGuiAttached", + "UiSession.verifySubject"), m_protocol); m_protocol.clear(); - //brower tab duplicate -> json startup with same client session + //browser tab duplicate -> json startup with same client session // this results in two UiSessions attached to the same client session final String clientSessionIdB; final String uiSessionIdB; @@ -367,8 +377,9 @@ public void testStartupThenBrowserTabDuplicate() throws IOException { assertEquals( Arrays.asList( "UiSession.init", - "UiSession.getOrCreateClientSession" + "UiSession.getOrCreateClientSession", // "Desktop.execGuiAttached" -> this is not called because there is already uiSessionA attached to the clientSession + "UiSession.verifySubject" ), m_protocol); m_protocol.clear(); @@ -379,13 +390,13 @@ public void testStartupThenBrowserTabDuplicate() throws IOException { final FixtureClientSession clientSession = (FixtureClientSession) store.getClientSessionMap().values().iterator().next(); - //brower tab A closed -> json unload + //browser tab A closed -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionIdA, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); unloadHandler.handlePost(req, resp); } - //brower tab B closed -> json unload + //browser tab B closed -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionIdB, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); @@ -450,11 +461,12 @@ public void testStartupThenBrowserTabDuplicateThenBrowserTabClose() throws IOExc "UiSession.getOrCreateClientSession", "ClientSession.execLoadSession", "Desktop.execOpened", - "Desktop.execGuiAttached"), + "Desktop.execGuiAttached", + "UiSession.verifySubject"), m_protocol); m_protocol.clear(); - //brower tab duplicate -> json startup with same client session + //browser tab duplicate -> json startup with same client session // this results in two UiSessions attached to the same client session final String clientSessionIdB; final String uiSessionIdB; @@ -472,8 +484,9 @@ public void testStartupThenBrowserTabDuplicateThenBrowserTabClose() throws IOExc assertEquals( Arrays.asList( "UiSession.init", - "UiSession.getOrCreateClientSession" + "UiSession.getOrCreateClientSession", // "Desktop.execGuiAttached" -> this is not called because there is already uiSessionA attached to the clientSession + "UiSession.verifySubject" ), m_protocol); m_protocol.clear(); @@ -482,7 +495,7 @@ public void testStartupThenBrowserTabDuplicateThenBrowserTabClose() throws IOExc assertNotEquals(uiSessionIdA, uiSessionIdB); assertEquals(2, store.countUiSessions()); - //brower tab A closed -> json unload + //browser tab A closed -> json unload final IClientSession clientSessionA = store.getClientSessionMap().get(clientSessionIdA); try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionIdA, null); @@ -508,7 +521,7 @@ public void testStartupThenBrowserTabDuplicateThenBrowserTabClose() throws IOExc assertEquals(1, store.countClientSessions()); assertEquals(1, store.countUiSessions()); - //brower tab B closed -> json unload + //browser tab B closed -> json unload try (BufferedServletOutputStream out = new BufferedServletOutputStream()) { final HttpServletRequest req = JsonTestUtility.createHttpServletRequest(httpSession, "/unload/" + uiSessionIdB, null); final HttpServletResponse resp = JsonTestUtility.createHttpServletResponse(out); @@ -563,6 +576,22 @@ public void dispose() { writeToProtocol("UiSession.dispose"); super.dispose(); } + + @Override + public void verifySubject(HttpServletRequest request) { + writeToProtocol("UiSession.verifySubject"); + + if (getClientSession() == null) { + return; + } + Subject subject = RunContext.CURRENT.get().getSubject(); + if (subject == null) { + return; + } + if (!ObjectUtility.equals(subject, getClientSession().getSubject())) { + Assert.fail("Subject didn't change and shouldn't be updated"); + } + } } @IgnoreBean diff --git a/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionTest.java b/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionTest.java index 8218c6b7a15..3b0d942d201 100644 --- a/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionTest.java +++ b/org.eclipse.scout.rt.ui.html.test/src/test/java/org/eclipse/scout/rt/ui/html/UiSessionTest.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import javax.security.auth.Subject; + import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSessionBindingListener; @@ -31,6 +33,10 @@ import org.eclipse.scout.rt.platform.context.RunContexts; import org.eclipse.scout.rt.platform.job.IFuture; import org.eclipse.scout.rt.platform.job.Jobs; +import org.eclipse.scout.rt.platform.security.JwtPrincipal; +import org.eclipse.scout.rt.platform.security.SamlPrincipal; +import org.eclipse.scout.rt.platform.security.SimplePrincipal; +import org.eclipse.scout.rt.platform.security.SimplePrincipalWithDelegation; import org.eclipse.scout.rt.testing.platform.BeanTestingHelper; import org.eclipse.scout.rt.testing.platform.job.JobTestUtil; import org.eclipse.scout.rt.testing.platform.runner.JUnitExceptionHandler; @@ -107,6 +113,30 @@ public void testLogout() { JsonTestUtility.endRequest(uiSession); } + @Test + public void testVerifySubject() { + UiSession uiSession = (UiSession) JsonTestUtility.createAndInitializeUiSession(); + + // Update Subject + Subject subject = new Subject(); + subject.getPrincipals().add(new SimplePrincipal("NewPrinciple0")); + subject.getPrincipals().add(new JwtPrincipal("NewPrinciple1", "testTokenString")); + subject.getPrincipals().add(new SamlPrincipal("NewPrinciple2", null)); + subject.getPrincipals().add(new SimplePrincipalWithDelegation("NewPrinciple3", null)); + subject.setReadOnly(); + + Subject oldSubject = uiSession.getClientSession().getSubject(); + + RunContext.CURRENT.get() + .withSubject(subject); + + uiSession.verifySubject(null); + + assertEquals(subject, uiSession.getClientSession().getSubject()); + assertNotEquals(oldSubject, subject); + JsonTestUtility.endRequest(uiSession); + } + @Test public void testSessionInvalidation() { UiSession uiSession = (UiSession) JsonTestUtility.createAndInitializeUiSession(); diff --git a/org.eclipse.scout.rt.ui.html/src/main/java/org/eclipse/scout/rt/ui/html/UiSession.java b/org.eclipse.scout.rt.ui.html/src/main/java/org/eclipse/scout/rt/ui/html/UiSession.java index 33640f5095e..4bb6e0243e8 100644 --- a/org.eclipse.scout.rt.ui.html/src/main/java/org/eclipse/scout/rt/ui/html/UiSession.java +++ b/org.eclipse.scout.rt.ui.html/src/main/java/org/eclipse/scout/rt/ui/html/UiSession.java @@ -63,7 +63,6 @@ import org.eclipse.scout.rt.platform.util.concurrent.FutureCancelledError; import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedError; import org.eclipse.scout.rt.platform.util.concurrent.TimedOutError; -import org.eclipse.scout.rt.server.commons.authentication.IAccessController; import org.eclipse.scout.rt.server.commons.servlet.CookieUtility; import org.eclipse.scout.rt.server.commons.servlet.HttpClientInfo; import org.eclipse.scout.rt.server.commons.servlet.UrlHints; @@ -812,8 +811,7 @@ public JSONObject getAlreadyProcessedResponse(JsonRequest jsonRequest) { /** * Verifies if an access controller has created a new {@link Subject} and replaces the current one on the - * {@link IClientSession} with the new one. An {@link IAccessController} can request this by setting the - * {@link IAccessController#UPDATED_SUBJECT} attribute on the request. + * {@link IClientSession} with the new one. */ @Override public void verifySubject(HttpServletRequest request) { @@ -824,7 +822,7 @@ public void verifySubject(HttpServletRequest request) { if (subject == null) { return; } - if (request.getAttribute(IAccessController.UPDATED_SUBJECT) != null) { + if (!ObjectUtility.equals(subject, m_clientSession.getSubject())) { m_clientSession.setSubject(subject); } }