From b0139cc958b0b8c12779d9a9612ef1646aba8fba Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Wed, 1 Nov 2023 22:21:49 +0100 Subject: [PATCH] #4266 - Improve backToProject call - Redirect to dashboard if possible and to dead-end page if not possible --- .../editor/state/AnnotatorStateImpl.java | 13 ++ .../AnnotatorDocumentNavigation.java | 2 + .../rendering/editorstate/AnnotatorState.java | 2 + .../clarin/webanno/api/ProjectService.java | 36 ++++- .../config/InceptionSecurityWebUIShared.java | 1 + .../webanno/project/ProjectServiceImpl.java | 31 ++++ .../webanno/ui/annotation/AnnotationPage.java | 2 + .../webanno/ui/core/page/ProjectPageBase.java | 19 ++- .../inception/ui/core/AccessDeniedPage.html | 30 ++++ .../inception/ui/core/AccessDeniedPage.java | 153 ++++++++++++++++++ .../ukp/inception/ui/core/ErrorPage.java | 2 - .../inception/ui/core/menubar/MenuBar.java | 26 ++- .../sidebar/CurationSidebarBehavior.java | 11 ++ 13 files changed, 306 insertions(+), 22 deletions(-) create mode 100644 inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.html create mode 100644 inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.java diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/editor/state/AnnotatorStateImpl.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/editor/state/AnnotatorStateImpl.java index d9482b46236..2b4a5c2b1f9 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/editor/state/AnnotatorStateImpl.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/editor/state/AnnotatorStateImpl.java @@ -236,6 +236,13 @@ public void setProject(Project aProject) project = aProject; } + @Override + public void clearProject() + { + project = null; + clearDocument(); + } + @Override public ScriptDirection getScriptDirection() { @@ -277,6 +284,12 @@ public int getNumberOfDocuments() return numberOfDocuments; } + @Override + public void clearDocument() + { + setDocument(null, null); + } + @Override public void setDocument(SourceDocument aDocument, List aDocuments) { diff --git a/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorDocumentNavigation.java b/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorDocumentNavigation.java index 931da7affad..de2be0d0596 100644 --- a/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorDocumentNavigation.java +++ b/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorDocumentNavigation.java @@ -26,6 +26,8 @@ public interface AnnotatorDocumentNavigation // --------------------------------------------------------------------------------------------- // Document // --------------------------------------------------------------------------------------------- + void clearDocument(); + SourceDocument getDocument(); void setDocument(SourceDocument aDocument, List aDocuments); diff --git a/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorState.java b/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorState.java index b2ea28daa60..857948df10b 100644 --- a/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorState.java +++ b/inception/inception-api-render/src/main/java/de/tudarmstadt/ukp/inception/rendering/editorstate/AnnotatorState.java @@ -107,6 +107,8 @@ public interface AnnotatorState @Override Project getProject(); + void clearProject(); + void setProject(Project aProject); // --------------------------------------------------------------------------------------------- diff --git a/inception/inception-api/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/ProjectService.java b/inception/inception-api/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/ProjectService.java index 24cb36b2f79..ada04aed518 100644 --- a/inception/inception-api/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/ProjectService.java +++ b/inception/inception-api/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/ProjectService.java @@ -508,7 +508,7 @@ void savePropertiesFile(Project aProject, InputStream aInputStream, String aFile * additional roles. * @param aMoreRoles * more roles. - * @return whether the user has any role in the project. + * @return whether the user has any of the roles in the project. */ boolean hasRole(User aUser, Project aProject, PermissionLevel aRole, PermissionLevel... aMoreRoles); @@ -527,11 +527,43 @@ boolean hasRole(User aUser, Project aProject, PermissionLevel aRole, * additional roles. * @param aMoreRoles * more roles. - * @return whether the user has any role in the project. + * @return whether the user has any of the roles in the project. */ boolean hasRole(String aUser, Project aProject, PermissionLevel aRole, PermissionLevel... aMoreRoles); + /** + * Check whether the given user has one or more roles in any project. Note that the split into + * two arguments is only for the compiler to be able to check if at least one role has been + * specified. The first role is not privileged over the other roles in any way! + * + * @param aUser + * a user. + * @param aRole + * at least one role must be given, but the check is against this role OR any of the + * additional roles. + * @param aMoreRoles + * more roles. + * @return whether the user has any of the roles in any project. + */ + boolean hasRoleInAnyProject(User aUser, PermissionLevel aRole, PermissionLevel... aMoreRoles); + + /** + * Check whether the given user has one or more roles in any project. Note that the split into + * two arguments is only for the compiler to be able to check if at least one role has been + * specified. The first role is not privileged over the other roles in any way! + * + * @param aUser + * a user. + * @param aRole + * at least one role must be given, but the check is against this role OR any of the + * additional roles. + * @param aMoreRoles + * more roles. + * @return whether the user has any of the roles in any project. + */ + boolean hasRoleInAnyProject(String aUser, PermissionLevel aRole, PermissionLevel... aMoreRoles); + // -------------------------------------------------------------------------------------------- // Methods related to other things // -------------------------------------------------------------------------------------------- diff --git a/inception/inception-app-webapp/src/main/java/de/tudarmstadt/ukp/inception/app/config/InceptionSecurityWebUIShared.java b/inception/inception-app-webapp/src/main/java/de/tudarmstadt/ukp/inception/app/config/InceptionSecurityWebUIShared.java index 8ca4947b28b..98e4b7e07ce 100644 --- a/inception/inception-app-webapp/src/main/java/de/tudarmstadt/ukp/inception/app/config/InceptionSecurityWebUIShared.java +++ b/inception/inception-app-webapp/src/main/java/de/tudarmstadt/ukp/inception/app/config/InceptionSecurityWebUIShared.java @@ -35,6 +35,7 @@ public static void accessToStaticResources( .antMatchers("/images/**").permitAll() // .antMatchers("/resources/**").permitAll() // .antMatchers("/whoops").permitAll() // + .antMatchers("/nowhere").permitAll() // .antMatchers("/about/**").permitAll() // .antMatchers("/wicket/resource/**").permitAll(); } diff --git a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectServiceImpl.java b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectServiceImpl.java index 5c1a9d3e2ef..fe5a3436f90 100644 --- a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectServiceImpl.java +++ b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectServiceImpl.java @@ -397,6 +397,37 @@ public boolean hasRole(String aUser, Project aProject, PermissionLevel aRole, .getSingleResult() > 0; } + @Override + @Transactional + public boolean hasRoleInAnyProject(User aUser, PermissionLevel aRole, + PermissionLevel... aMoreRoles) + { + return hasRoleInAnyProject(aUser.getUsername(), aRole, aMoreRoles); + } + + @Override + @Transactional + public boolean hasRoleInAnyProject(String aUser, PermissionLevel aRole, + PermissionLevel... aMoreRoles) + { + Validate.notNull(aRole, "hasRoleInAnyProject() requires at least one role to check"); + + var roles = new LinkedHashSet<>(); + roles.add(aRole); + if (aMoreRoles != null) { + roles.addAll(asList(aMoreRoles)); + } + + String query = String.join("\n", // + "SELECT COUNT(*) FROM ProjectPermission ", // + "WHERE user = :user AND level IN (:roles)"); + + return entityManager.createQuery(query, Long.class) // + .setParameter("user", aUser) // + .setParameter("roles", roles) // + .getSingleResult() > 0; + } + @Deprecated @Override @Transactional diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java index 9aae269f1e2..bec13372cb8 100755 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java @@ -610,6 +610,7 @@ protected void handleParameters(StringValue aDocumentParameter, StringValue aFoc var requestedUser = userRepository.get(aUserParameter.toString()); if (requestedUser == null) { failWithDocumentNotFound("User not found [" + aUserParameter + "]"); + return; } else { LOG.trace("Changing data owner: {}", requestedUser); @@ -622,6 +623,7 @@ protected void handleParameters(StringValue aDocumentParameter, StringValue aFoc String.valueOf(project.getId()), doc.getId(), state.getUser().getUsername())) { failWithDocumentNotFound("Access to document [" + aDocumentParameter + "] in project [" + project.getName() + "] is denied"); + return; } // If we arrive here and the document is not null, then we have a change of document diff --git a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/core/page/ProjectPageBase.java b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/core/page/ProjectPageBase.java index 694f587de47..9e03daf4c2f 100644 --- a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/core/page/ProjectPageBase.java +++ b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/core/page/ProjectPageBase.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.clarin.webanno.ui.core.page; +import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.MANAGER; import static java.lang.String.format; import static java.util.stream.Collectors.joining; @@ -41,6 +42,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; +import de.tudarmstadt.ukp.inception.ui.core.AccessDeniedPage; import de.tudarmstadt.ukp.inception.ui.core.config.DashboardProperties; public abstract class ProjectPageBase @@ -75,6 +77,7 @@ protected final void requireAnyProjectRole(User aUser) if (aUser == null || !projectService.hasAnyRole(aUser, project)) { getSession().error(format("To access the [%s] you need to be a member of the project", getClass().getSimpleName())); + backToProjectPage(); } } @@ -97,22 +100,18 @@ protected final void requireProjectRole(User aUser, PermissionLevel aRole, public void backToProjectPage() { - Class projectDashboard = WicketObjects.resolveClass( - "de.tudarmstadt.ukp.inception.ui.core.dashboard.project.ProjectDashboardPage"); - - // If the current user cannot access the dashboard, at least update the feedback panel on - // the current page so that the user can see the error message that has most likely been - // queued - if (!projectService.hasRole(userService.getCurrentUsername(), getProject(), - PermissionLevel.MANAGER, + // If the current user cannot access the dashboard, send them to an access denied page + if (!projectService.hasRole(userService.getCurrentUsername(), getProject(), MANAGER, dashboardProperties.getAccessibleByRoles().toArray(PermissionLevel[]::new))) { getRequestCycle().find(AjaxRequestTarget.class) .ifPresent(_target -> _target.addChildren(getPage(), IFeedback.class)); - return; + throw new RestartResponseException(AccessDeniedPage.class); } - PageParameters pageParameters = new PageParameters(); + var pageParameters = new PageParameters(); setProjectPageParameter(pageParameters, getProject()); + Class projectDashboard = WicketObjects.resolveClass( + "de.tudarmstadt.ukp.inception.ui.core.dashboard.project.ProjectDashboardPage"); throw new RestartResponseException(projectDashboard, pageParameters); } diff --git a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.html b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.html new file mode 100644 index 00000000000..44968e93c25 --- /dev/null +++ b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.html @@ -0,0 +1,30 @@ + + + + + +
+
+

No access

+

There is nothing here for you...

+
+
+
+ + diff --git a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.java b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.java new file mode 100644 index 00000000000..1e4515b106c --- /dev/null +++ b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/AccessDeniedPage.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * 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 de.tudarmstadt.ukp.inception.ui.core; + +import static de.tudarmstadt.ukp.clarin.webanno.support.JSONUtil.toPrettyJsonString; +import static java.util.Arrays.asList; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.parseMediaTypes; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.apache.wicket.protocol.http.servlet.ErrorAttributes; +import org.apache.wicket.protocol.http.servlet.ServletWebRequest; +import org.apache.wicket.request.Request; +import org.apache.wicket.request.Url; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.handler.TextRequestHandler; +import org.apache.wicket.request.http.WebRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.wicketstuff.annotation.mount.MountPath; + +import com.giffing.wicket.spring.boot.context.scan.WicketAccessDeniedPage; + +import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ApplicationPageBase; + +@WicketAccessDeniedPage +@MountPath("/nowhere") +public class AccessDeniedPage + extends ApplicationPageBase +{ + private static final long serialVersionUID = 7848496813044538495L; + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public AccessDeniedPage() + { + if (isJsonResponseRequested() || StringUtils.startsWith(getRequestUri(), "/api")) { + produceJsonResponseIfRequested(); + } + } + + private void produceJsonResponseIfRequested() + { + String response; + try { + response = toPrettyJsonString(Map.of( // + "messages", asList( // + Map.of( // + "level", "ERROR", // + "message", getStatusReasonPhrase())))); + } + catch (IOException e) { + response = "{\"messages\": [{\"level\": \"ERROR\", \"message\": \"Unable to render error.\"}] }"; + LOG.error("Unable to render error message", e); + } + + getRequestCycle().scheduleRequestHandlerAfterCurrent( + new TextRequestHandler(APPLICATION_JSON.toString(), null, response)); + } + + private boolean isJsonResponseRequested() + { + if (!(getRequestCycle().getRequest() instanceof WebRequest)) { + return false; + } + + WebRequest request = (WebRequest) getRequestCycle().getRequest(); + if (!APPLICATION_JSON.isPresentIn(parseMediaTypes(request.getHeader("Accept")))) { + return false; + } + + return true; + } + + private Optional getErrorAttributes() + { + RequestCycle cycle = RequestCycle.get(); + + Request req = cycle.getRequest(); + if (req instanceof ServletWebRequest) { + ServletWebRequest webRequest = (ServletWebRequest) req; + ErrorAttributes errorAttributes = ErrorAttributes.of(webRequest.getContainerRequest(), + webRequest.getFilterPrefix()); + + return Optional.ofNullable(errorAttributes); + } + + return Optional.empty(); + } + + private String getStatusReasonPhrase() + { + return getErrorAttributes() // + .map(ErrorAttributes::getStatusCode) // + .map(code -> { + try { + return HttpStatus.valueOf(code).getReasonPhrase(); + } + catch (IllegalArgumentException e) { + return null; + } + }) // + .orElse(null); + } + + private String getRequestUri() + { + Optional uri = getErrorAttributes().map(ErrorAttributes::getRequestUri); + + if (uri.isPresent()) { + return uri.get(); + } + + Url url = RequestCycle.get().getRequest().getOriginalUrl(); + if (url != null) { + return "/" + url.toString(); + } + + return null; + } + + @Override + public boolean isErrorPage() + { + return true; + } + + @Override + public boolean isVersioned() + { + return false; + } +} diff --git a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/ErrorPage.java b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/ErrorPage.java index 5d6aac99f9d..e958077f21a 100644 --- a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/ErrorPage.java +++ b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/ErrorPage.java @@ -51,7 +51,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.wicketstuff.annotation.mount.MountPath; -import com.giffing.wicket.spring.boot.context.scan.WicketAccessDeniedPage; import com.giffing.wicket.spring.boot.context.scan.WicketExpiredPage; import com.giffing.wicket.spring.boot.context.scan.WicketInternalErrorPage; @@ -61,7 +60,6 @@ @WicketInternalErrorPage @WicketExpiredPage -@WicketAccessDeniedPage @MountPath("/whoops") public class ErrorPage extends ApplicationPageBase diff --git a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/menubar/MenuBar.java b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/menubar/MenuBar.java index 0ba1b9f8601..7ee63fa6f68 100644 --- a/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/menubar/MenuBar.java +++ b/inception/inception-ui-core/src/main/java/de/tudarmstadt/ukp/inception/ui/core/menubar/MenuBar.java @@ -121,8 +121,11 @@ protected void populateItem(ListItem aItem) private boolean isMenubarVisibleToCurrentUser() { - // E.g. on the invite page, we do not have a user object, but we would still like to see - // the empty menu bar saying "INCEpTION". + // When we have no project, we would like to show the menubar, e.g. on the + // - project overview page + // When we have no user, we would also like to show the the empty menu bar saying + // "INCEpTION", e.g. on the: + // - invite page if (!user.isPresent().getObject() || !project.isPresent().getObject()) { return true; } @@ -132,14 +135,21 @@ private boolean isMenubarVisibleToCurrentUser() return true; } + var eligibleRoles = dashboardProperties.getAccessibleByRoles() + .toArray(PermissionLevel[]::new); + + // If we do not know the current project, we make the menu accessible if the user has an + // eligible role in any project - they could then use the menubar to switch to a project + // where they have more permissions. // The project might be null if it is in the process of being created. Normally, this can - // only be done by admisn and project creators that are handled above - so returning false + // only be done by admin and project creators that are handled above - so returning false // here is really just a sanity fallback that should never kick in. - if (project.getObject().getId() == null) { - return false; - } + // if (!project.isPresent().getObject() || project.getObject().getId() == null) { + // return projectService.hasRoleInAnyProject(user.getObject(), MANAGER, eligibleRoles); + // } - var roles = dashboardProperties.getAccessibleByRoles().toArray(PermissionLevel[]::new); - return projectService.hasRole(user.getObject(), project.getObject(), MANAGER, roles); + // If we know the project, we show the menubar if the user has an eligible role + return projectService.hasRole(user.getObject(), project.getObject(), MANAGER, + eligibleRoles); } } diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java index cbaabb86e12..f608fca854e 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.ui.curation.sidebar; +import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.CURATOR; import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.CURATION_USER; import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.setProjectPageParameter; import static java.lang.invoke.MethodHandles.lookup; @@ -31,6 +32,7 @@ import org.apache.wicket.spring.injection.annot.SpringBean; import org.slf4j.Logger; +import de.tudarmstadt.ukp.clarin.webanno.api.ProjectService; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; @@ -54,6 +56,7 @@ public class CurationSidebarBehavior private @SpringBean CurationSidebarService curationSidebarService; private @SpringBean UserDao userService; + private @SpringBean ProjectService projectService; @Override public void onEvent(Component aComponent, IEvent aEvent) @@ -88,6 +91,13 @@ public void onEvent(Component aComponent, IEvent aEvent) var project = doc.getProject(); var dataOwner = event.getDocumentOwner(); + if (!projectService.hasRole(sessionOwner, project, CURATOR)) { + LOG.trace( + "Session owner [{}] is not a curator and can therefore not manage curation mode using URL parameters", + sessionOwner); + return; + } + LOG.trace("Curation sidebar reacting to [{}]@{} being opened by [{}]", dataOwner, doc, sessionOwner); @@ -129,6 +139,7 @@ private void handleSessionActivation(AnnotationPageBase aPage, PageParameters aP SourceDocument aDoc, String aSessionOwner) { var project = aDoc.getProject(); + var curationSessionParameterValue = aParams.get(PARAM_CURATION_SESSION); if (curationSessionParameterValue.isEmpty()) { return;