diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java index 173a8e3c97e..2e764568932 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java @@ -112,7 +112,7 @@ public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, FSIterator it = aCas.getAnnotationIndex(type).iterator(); // Skip annotations whose start is before the start parameter. - while (it.isValid() && (it.get()).getBegin() < aWindowBegin) { + while (it.isValid() && (it.get()).getBegin() < 0) { it.moveToNext(); } 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 1ecd659c99a..847038a4fdb 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-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts index 78ad512554a..4006c964573 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts +++ b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts @@ -334,8 +334,10 @@ export class ApacheAnnotatorVisualizer { const coreBegin = Math.max(begin, viewportBegin) const coreEnd = Math.min(end, viewportEnd) - this.renderHighlight(span, coreBegin, coreEnd, attributes) - fragmentCount++ + if (coreBegin <= coreEnd) { + this.renderHighlight(span, coreBegin, coreEnd, attributes) + fragmentCount++ + } attributes.class += ' iaa-zero-width' // Prevent prefix/suffix fragmens from being cleaned up if (!(viewportBegin <= begin && begin <= viewportEnd)) { 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 7497d2e820e..0bc69153d45 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 f33ffbe0265..5e12dc1b4a3 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;