Skip to content

Commit

Permalink
#4603 - Bulk actions in search sidebar should not be greyed-out when …
Browse files Browse the repository at this point in the history
…in sidebar curation mode

- Use DocumentAccess to check if document is editable
- Also use DocumentAccess in the AnnotationPage to check if document is editable
  • Loading branch information
reckart committed Mar 7, 2024
1 parent fd7c71c commit e7d92e8
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
*/
package de.tudarmstadt.ukp.clarin.webanno.api.annotation.page;

import static de.tudarmstadt.ukp.clarin.webanno.model.Mode.CURATION;
import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.ANNOTATOR;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.CURATION_FINISHED;
import static de.tudarmstadt.ukp.inception.rendering.selection.FocusPosition.CENTERED;
import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER;
import static java.lang.String.format;
Expand Down Expand Up @@ -49,6 +46,7 @@
import org.apache.wicket.util.string.StringValueConversionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.wicketstuff.urlfragment.UrlFragment;
import org.wicketstuff.urlfragment.UrlParametersReceivingBehavior;

Expand All @@ -62,6 +60,7 @@
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentService;
import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler;
import de.tudarmstadt.ukp.inception.project.api.ProjectService;
Expand All @@ -88,6 +87,7 @@ public abstract class AnnotationPageBase

private @SpringBean AnnotationSchemaService annotationService;
private @SpringBean DocumentService documentService;
private @SpringBean DocumentAccess documentAccess;
private @SpringBean UserPreferencesService userPreferenceService;
private @SpringBean UserDao userRepository;
private @SpringBean ProjectService projectService;
Expand Down Expand Up @@ -446,36 +446,20 @@ protected void loadPreferences() throws IOException

public void ensureIsEditable() throws NotEditableException
{
AnnotatorState state = getModelObject();
var state = getModelObject();

if (state.getDocument() == null) {
throw new NotEditableException("No document selected");
}

// If curating (check mode for curation page and user for curation sidebar),
// then it is editable unless the curation is finished
if (state.getMode() == CURATION || CURATION_USER.equals(state.getUser().getUsername())) {
if (state.getDocument().getState().equals(CURATION_FINISHED)) {
throw new NotEditableException("Curation is already finished. You can put it back "
+ "into progress via the monitoring page.");
}

return;
}

if (getModelObject().isUserViewingOthersWork(userRepository.getCurrentUsername())) {
throw new NotEditableException(
"Viewing another users annotations - document is read-only!");
}
var sessionOwner = userRepository.getCurrentUser();

if (isAnnotationFinished()) {
throw new NotEditableException("This document is already closed for user ["
+ state.getUser().getUsername() + "]. Please ask your "
+ "project manager to re-open it via the monitoring page.");
try {
documentAccess.assertCanEditAnnotationDocument(sessionOwner, state.getDocument(),
state.getUser().getUsername());
}

if (!projectService.hasRole(userRepository.getCurrentUsername(), getProject(), ANNOTATOR)) {
throw new NotEditableException("You are not an annotator in this project.");
catch (AccessDeniedException e) {
throw new NotEditableException(e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.inception.documents;
package de.tudarmstadt.ukp.inception.documents.api;

import org.springframework.security.access.AccessDeniedException;

import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.security.AccessCheckingBean;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.inception.documents.config.DocumentServiceAutoConfiguration;

/**
* <p>
* This class is exposed as a Spring Component via
* {@link DocumentServiceAutoConfiguration#documentAccess}.
* </p>
*/
public interface DocumentAccess
extends AccessCheckingBean
{
Expand All @@ -39,5 +35,10 @@ boolean canViewAnnotationDocument(String aUser, String aProjectId, long aDocumen
boolean canEditAnnotationDocument(String aUser, String aProjectId, long aDocumentId,
String aAnnotator);

void assertCanEditAnnotationDocument(User aSessionOwner,
SourceDocument aDocument, String aDataOwner)
throws AccessDeniedException;

boolean canExportAnnotationDocument(User aUser, Project aProject);

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.ANNOTATOR;
import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.CURATOR;
import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.MANAGER;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.CURATION_FINISHED;
import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER;
import static org.apache.commons.collections4.CollectionUtils.containsAny;

import javax.persistence.NoResultException;
Expand All @@ -29,12 +31,12 @@
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;

import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentService;
import de.tudarmstadt.ukp.inception.documents.config.DocumentServiceAutoConfiguration;
import de.tudarmstadt.ukp.inception.project.api.ProjectService;
Expand Down Expand Up @@ -139,34 +141,70 @@ public boolean canEditAnnotationDocument(String aSessionOwner, String aProjectId
aSessionOwner, aProjectId, aDocumentId, aAnnotator);

try {
User user = getUser(aSessionOwner);
Project project = getProject(aProjectId);
var sessionOwner = getUser(aSessionOwner);
var project = getProject(aProjectId);
var doc = documentService.getSourceDocument(project.getId(), aDocumentId);

// Does the user have the permission to access the project at all?
if (!projectService.hasRole(user, project, ANNOTATOR)) {
return false;
assertCanEditAnnotationDocument(sessionOwner, doc, aAnnotator);

return true;
}
catch (NoResultException | AccessDeniedException e) {
// If any object does not exist, the user cannot edit
return false;
}
}

@Override
public void assertCanEditAnnotationDocument(User aSessionOwner, SourceDocument aDocument,
String aDataOwner)
{
var project = aDocument.getProject();

// Is the user a curator?
if (projectService.hasRole(aSessionOwner, project, CURATOR)) {
// If curation is already done, document is no longer editable
if (CURATION_USER.equals(aDataOwner)) {
if (aDocument.getState() == CURATION_FINISHED) {
throw new AccessDeniedException(
"Curation is already finished. You can put it back "
+ "into progress via the monitoring page.");
}

return; // Access granted
}

// Users can edit their own annotations
if (!aSessionOwner.equals(aAnnotator)) {
return false;
// Fall-through - user may still be an annotator
}

// Is the user an annotator?
if (projectService.hasRole(aSessionOwner, project, ANNOTATOR)) {
// Annotators can edit their own annotations
if (!aSessionOwner.getUsername().equals(aDataOwner)) {
throw new AccessDeniedException(
"Viewing another users annotations - document is read-only!");
}

// Blocked documents cannot be edited
SourceDocument doc = documentService.getSourceDocument(project.getId(), aDocumentId);
if (documentService.existsAnnotationDocument(doc, aAnnotator)) {
AnnotationDocument aDoc = documentService.getAnnotationDocument(doc, aAnnotator);
// Blocked or finished documents cannot be edited
if (documentService.existsAnnotationDocument(aDocument, aDataOwner)) {
var aDoc = documentService.getAnnotationDocument(aDocument, aDataOwner);
if (aDoc.getState() == AnnotationDocumentState.FINISHED) {
throw new AccessDeniedException("This document is already closed for user ["
+ aDataOwner + "]. Please ask your "
+ "project manager to re-open it via the monitoring page.");
}

if (aDoc.getState() == AnnotationDocumentState.IGNORE) {
return false;
throw new AccessDeniedException("This document is blocked for user ["
+ aDataOwner + "]. Please ask your "
+ "project manager if you believe this is wrong.");
}
}

return true;
}
catch (NoResultException | AccessDeniedException e) {
// If any object does not exist, the user cannot edit
return false;
return; // Access granted
}

throw new AccessDeniedException("You have no permission to edit this document");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasStorageService;
import de.tudarmstadt.ukp.clarin.webanno.api.export.DocumentImportExportService;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.inception.documents.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.DocumentAccessImpl;
import de.tudarmstadt.ukp.inception.documents.DocumentServiceImpl;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentService;
import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties;
import de.tudarmstadt.ukp.inception.documents.exporters.SourceDocumentExporter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
import de.tudarmstadt.ukp.inception.annotation.events.FeatureValueUpdatedEvent;
import de.tudarmstadt.ukp.inception.annotation.events.PreparingToOpenDocumentEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport;
import de.tudarmstadt.ukp.inception.documents.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentService;
import de.tudarmstadt.ukp.inception.editor.AnnotationEditorBase;
import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtensionRegistry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.open.OpenDocumentDialog;
import de.tudarmstadt.ukp.inception.documents.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.project.api.ProjectService;
import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink;
import de.tudarmstadt.ukp.inception.support.wicket.input.InputBehavior;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.actionbar.export.ExportDocumentDialog;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.inception.documents.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.project.api.ProjectService;
import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink;
import de.tudarmstadt.ukp.inception.support.wicket.input.InputBehavior;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.wicketstuff.event.annotation.OnEvent;

import de.agilecoders.wicket.core.markup.html.bootstrap.navigation.BootstrapPagingNavigator.Size;
Expand All @@ -82,6 +83,7 @@
import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.sidebar.AnnotationSidebar_ImplBase;
import de.tudarmstadt.ukp.inception.annotation.events.BulkAnnotationEvent;
Expand All @@ -90,6 +92,7 @@
import de.tudarmstadt.ukp.inception.app.ui.search.sidebar.options.DeleteAnnotationsOptions;
import de.tudarmstadt.ukp.inception.app.ui.search.sidebar.options.SearchOptions;
import de.tudarmstadt.ukp.inception.bootstrap.IconToggleBox;
import de.tudarmstadt.ukp.inception.documents.api.DocumentAccess;
import de.tudarmstadt.ukp.inception.documents.api.DocumentService;
import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler;
import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState;
Expand Down Expand Up @@ -144,6 +147,7 @@ public class SearchAnnotationSidebar
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private @SpringBean DocumentService documentService;
private @SpringBean DocumentAccess documentAccess;
private @SpringBean AnnotationSchemaService annotationService;
private @SpringBean SearchService searchService;
private @SpringBean UserDao userRepository;
Expand Down Expand Up @@ -607,6 +611,7 @@ public void actionApplyToSelectedResults(AjaxRequestTarget aTarget, Operation aC
return;
}

var sessionOwner = userRepository.getCurrentUser();
var dataOwner = getAnnotationPage().getModelObject().getUser();
var layer = getModelObject().getSelectedAnnotationLayer();
try {
Expand All @@ -628,17 +633,12 @@ public void actionApplyToSelectedResults(AjaxRequestTarget aTarget, Operation aC
var sourceDoc = documentService.getSourceDocument(state.getProject().getId(),
documentId);

var annoDoc = documentService.createOrGetAnnotationDocument(sourceDoc, dataOwner);

switch (annoDoc.getState()) {
case FINISHED: // fall-through
case IGNORE:
// Skip processing any documents which are finished or ignored
if (!canAccessDocument(sessionOwner, sourceDoc, dataOwner)) {
continue;
default:
// Do nothing
}

var annoDoc = documentService.createOrGetAnnotationDocument(sourceDoc, dataOwner);

// Holder for lazily-loaded CAS
Optional<CAS> cas = Optional.empty();

Expand Down Expand Up @@ -695,6 +695,18 @@ public void actionApplyToSelectedResults(AjaxRequestTarget aTarget, Operation aC
getAnnotationPage().actionRefreshDocument(aTarget);
}

private boolean canAccessDocument(User sessionOwner, SourceDocument sourceDoc, User dataOwner)
{
try {
documentAccess.assertCanEditAnnotationDocument(sessionOwner, sourceDoc,
dataOwner.getUsername());
return true;
}
catch (AccessDeniedException e) {
return false;
}
}

private void createAnnotationAtSearchResult(SourceDocument aDocument, CAS aCas,
SpanAdapter aAdapter, SearchResult aSearchResult, BulkOperationResult aBulkResult)
throws AnnotationException
Expand Down

0 comments on commit e7d92e8

Please sign in to comment.