Skip to content
Permalink
Browse files Browse the repository at this point in the history
XWIKI-16285: Error when accessing deleted document
  • Loading branch information
surli committed Nov 2, 2022
1 parent b8caf47 commit d9e9475
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 27 deletions.
Expand Up @@ -22,18 +22,23 @@
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.util.DefaultParameterizedType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceResolver;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface;
import com.xpn.xwiki.util.Programming;
import com.xpn.xwiki.web.Utils;

/**
* Information about a deleted document in the recycle bin.
Expand All @@ -52,6 +57,8 @@ public class DeletedDocument extends Api
*/
private final XWikiDeletedDocument deletedDoc;

private UserReferenceResolver<DocumentReference> userReferenceResolver;

/**
* Simple constructor, initializes a new API object with the current {@link com.xpn.xwiki.XWikiContext context} and
* the specified protected {@link com.xpn.xwiki.doc.XWikiDeletedDocument deleted document} object.
Expand Down Expand Up @@ -134,22 +141,46 @@ public String getBatchId()
return this.deletedDoc.getBatchId();
}

private UserReferenceResolver<DocumentReference> getUserReferenceResolver()
{
if (this.userReferenceResolver == null) {
this.userReferenceResolver = Utils.getComponent(
new DefaultParameterizedType(null, UserReferenceResolver.class, DocumentReference.class), "document");
}
return this.userReferenceResolver;
}

private boolean hasAccess(Right right)
{
UserReference userReference = getUserReferenceResolver().resolve(this.context.getUserReference());
XWikiRecycleBinStoreInterface recycleBinStore = this.context.getWiki().getRecycleBinStore();
return recycleBinStore.hasAccess(right, userReference, this.deletedDoc);
}

/**
* Check if the current user has the right to restore the document.
*
* @return {@code true} if the current user can restore this document, {@code false} otherwise
*/
public boolean canUndelete()
{
try {
return hasAccessLevel(ADMIN_RIGHT, getFullName()) || hasAccessLevel("undelete", getFullName())
|| (Objects.equals(this.context.getUserReference(), getDeleterReference())
&& hasAccess(Right.EDIT, getDocumentReference()));
} catch (XWikiException ex) {
// Public APIs should not throw exceptions
LOGGER.warn("Exception while checking if entry [{}] can be restored from the recycle bin", getId(), ex);
return false;
}
return hasAccess(Right.EDIT);
}

/**
* Check if the current user has the right to view the deleted document.
* This is allowed either if the user has admin right on the original reference of the doc, or if they were the
* original user who deleted it.
*
* @return {code true} if the current user is allowed to view the deleted document.
* @since 14.10RC1
* @since 14.4.7
* @since 13.10.11
*/
@Unstable
public boolean canView()
{
return hasAccess(Right.VIEW);
}

/**
Expand Down
Expand Up @@ -21,6 +21,11 @@

import org.xwiki.component.annotation.Role;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;

import com.xpn.xwiki.XWikiException;

Expand Down Expand Up @@ -56,4 +61,25 @@
* @throws XWikiException when failing to load the document revision
*/
XWikiDocument getRevision(XWikiDocument document, String revision) throws XWikiException;

/**
* Check if access is granted on the given document revision, for the given user and right: if the access is not
* granted this method will throw an {@link AccessDeniedException}.
* This method allows each revision provider to have their own check depending on the type of revision.
*
* @param right the right for which to check if access is granted
* @param userReference the user for whom to check access
* @param documentReference the reference of the document
* @param revision the revision of the document
* @throws AuthorizationException if the access is denied
* @throws XWikiException in case of problem when loading the revision
* @since 14.10RC1
* @since 14.4.7
* @since 13.10.11
*/
@Unstable
default void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
String revision) throws AuthorizationException, XWikiException
{
}
}
Expand Up @@ -24,6 +24,7 @@
import java.util.Locale;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.xwiki.localization.LocaleUtils;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
Expand Down Expand Up @@ -394,4 +395,14 @@ public String getBatchId()
{
return batchId;
}

@Override
public String toString()
{
return new ToStringBuilder(this)
.append("id", id)
.append("fullName", fullName)
.append("locale", locale)
.toString();
}
}
Expand Up @@ -24,10 +24,14 @@
import javax.inject.Provider;
import javax.inject.Singleton;

import org.apache.commons.lang3.tuple.Pair;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;

import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.DocumentRevisionProvider;
Expand All @@ -54,10 +58,8 @@ public class DefaultDocumentRevisionProvider extends AbstractDocumentRevisionPro
@Named("database")
private DocumentRevisionProvider databaseDocumentRevisionProvider;

@Override
public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
private Pair<String, String> parseRevision(String revision)
{
// Parse the version
String revisionPrefix = null;
if (revision != null) {
int revisionPrefixIndex = revision.indexOf(':');
Expand All @@ -71,7 +73,11 @@ public XWikiDocument getRevision(DocumentReference reference, String revision) t
} else {
shortRevision = revision;
}
return Pair.of(revisionPrefix, shortRevision);
}

private DocumentRevisionProvider getProvider(String revisionPrefix) throws XWikiException
{
// Find the provider
DocumentRevisionProvider provider = this.databaseDocumentRevisionProvider;
if (revisionPrefix != null) {
Expand All @@ -80,12 +86,30 @@ public XWikiDocument getRevision(DocumentReference reference, String revision) t
try {
provider = componentManager.getInstance(DocumentRevisionProvider.class, revisionPrefix);
} catch (ComponentLookupException e) {
throw new XWikiException("Failed to get revision provider for revision [" + revision + "]", e);
throw new XWikiException("Failed to get revision provider for revision [" + revisionPrefix + "]",
e);
}
}
}
return provider;
}

@Override
public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
{
Pair<String, String> parsedRevision = parseRevision(revision);

// Load the document revision
return provider.getRevision(reference, shortRevision);
return getProvider(parsedRevision.getLeft()).getRevision(reference, parsedRevision.getRight());
}

@Override
public void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
String revision) throws AuthorizationException, XWikiException
{
Pair<String, String> parsedRevision = parseRevision(revision);

getProvider(parsedRevision.getLeft())
.checkAccess(right, userReference, documentReference, parsedRevision.getRight());
}
}
Expand Up @@ -26,6 +26,9 @@

import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
Expand Down Expand Up @@ -61,12 +64,23 @@ public XWikiDocument getRevision(DocumentReference reference, String revision) t
}

return null;

}

@Override
public XWikiDocument getRevision(XWikiDocument document, String revision) throws XWikiException
{
return getRevision(document != null ? document.getDocumentReferenceWithLocale() : null, revision);
}

@Override
public void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
String revision) throws AuthorizationException, XWikiException
{
XWikiContext xcontext = this.xcontextProvider.get();

XWikiDeletedDocument deletedDocument = xcontext.getWiki().getDeletedDocument(Long.valueOf(revision), xcontext);
if (deletedDocument != null) {
xcontext.getWiki().getRecycleBinStore().checkAccess(right, userReference, deletedDocument);
}
}
}
Expand Up @@ -22,6 +22,7 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import javax.inject.Inject;
import javax.inject.Named;
Expand All @@ -39,6 +40,12 @@
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceSerializer;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
Expand All @@ -58,6 +65,13 @@
@Singleton
public class XWikiHibernateRecycleBinStore extends XWikiHibernateBaseStore implements XWikiRecycleBinStoreInterface
{
@Inject
private AuthorizationManager authorizationManager;

@Inject
@Named("document")
private UserReferenceSerializer<DocumentReference> userReferenceSerializer;

/**
* {@link HibernateCallback} used to retrieve from the recycle bin store the deleted versions of a document.
*/
Expand Down Expand Up @@ -430,4 +444,31 @@ public void deleteFromRecycleBin(final long index, XWikiContext context, boolean
return null;
});
}

@Override
public void checkAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
throws AuthorizationException
{
if (!this.hasAccess(right, userReference, deletedDocument)) {
throw new AuthorizationException(
String.format("[%s] cannot access deleted document [%s] for right [%s]: "
+ "only admin or deleter of the document are authorized",
userReference, deletedDocument, right));
}
}

@Override
public boolean hasAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
{
DocumentReference documentReference = deletedDocument.getDocumentReference();
DocumentReference userDocReference = this.userReferenceSerializer.serialize(userReference);

boolean result = false;
if (this.authorizationManager.hasAccess(Right.ADMIN, userDocReference, documentReference)
|| (Objects.equals(deletedDocument.getDeleterReference(), userDocReference)
&& this.authorizationManager.hasAccess(right, userDocReference, documentReference))) {
result = true;
}
return result;
}
}
Expand Up @@ -22,6 +22,10 @@
import java.util.Date;

import org.xwiki.component.annotation.Role;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
Expand Down Expand Up @@ -232,4 +236,39 @@ default void deleteFromRecycleBin(long index, XWikiContext context, boolean bTra
// unpredictable.
deleteFromRecycleBin(new XWikiDocument(), index, context, bTransaction);
}

/**
* Check if the given deleted document can be accessed for the given right by the given user.
* This method only throw the {@link AuthorizationException} if the right is not granted.
*
* @param right the right to check access for
* @param userReference the user for whom to check access
* @param deletedDocument the document to be accessed
* @throws AuthorizationException if the user doesn't have appropriate right
* @since 14.10RC1
* @since 14.4.7
* @since 13.10.11
*/
@Unstable
default void checkAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument) throws
AuthorizationException
{
}

/**
* Check if the given deleted document can be accessed for the given right by the given user.
*
* @param right the right to check access for
* @param userReference the user for whom to check access
* @param deletedDocument the document to be accessed
* @return {@code true} if the user have appropriate right
* @since 14.10RC1
* @since 14.4.7
* @since 13.10.11
*/
@Unstable
default boolean hasAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
{
return false;
}
}

0 comments on commit d9e9475

Please sign in to comment.