/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.security.authorization.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.context.Execution;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.security.SecurityReference;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.EntityTypeNotSupportedException;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.authorization.RightSet;
import org.xwiki.security.authorization.RuleState;
import org.xwiki.security.authorization.SecurityEntryReader;
import org.xwiki.security.authorization.SecurityRule;
import org.xwiki.security.authorization.SecurityRuleEntry;
import org.xwiki.security.internal.XWikiConstants;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
/**
* The default implementation of the security rules reader, which reads rules from documents in a wiki.
*
* @version $Id: 9fdfc015c9b9133f4e6999ec119f34d1f137a106 $
* @since 4.0M2
*/
@Component
@Singleton
public class DefaultSecurityEntryReader implements SecurityEntryReader
{
/** A security rules to deny everyone the edit right by allowing edit to no one. */
private static final SecurityRule DENY_EDIT = new AllowEditToNoOneRule();
/** Right set allowed for main wiki owner. */
private static final Set<Right> MAINWIKIOWNER_RIGHTS = new RightSet(Right.PROGRAM);
/** Right set allowed for wiki owner. */
private static final Set<Right> OWNER_RIGHTS = new RightSet(Right.ADMIN);
/** Right set allowed for document creators. */
private static final Set<Right> CREATOR_RIGHTS = new RightSet(Right.CREATOR);
/** Resolver for user and group names. */
@Inject
@Named("user")
private DocumentReferenceResolver<String> resolver;
/** Execution object. */
@Inject
private Execution execution;
/**
* @return the current {@code XWikiContext}
*/
private XWikiContext getXWikiContext() {
return ((XWikiContext) execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY));
}
/**
* Internal implementation of the SecurityRuleEntry.
*/
private final class InternalSecurityRuleEntry extends AbstractSecurityRuleEntry
{
/** Reference of the related entity. */
private final SecurityReference reference;
/** The list of objects. */
private final Collection<SecurityRule> rules;
/**
* @param reference reference of the related entity
* @param rules collection of security rules applied on the entity.
*/
private InternalSecurityRuleEntry(SecurityReference reference, Collection<SecurityRule> rules)
{
this.reference = reference;
this.rules = Collections.unmodifiableCollection(rules);
}
/**
* @return the reference of the related entity
*/
@Override
public SecurityReference getReference()
{
return reference;
}
/**
* @return all rules available for this entity
*/
@Override
public Collection<SecurityRule> getRules()
{
return rules;
}
}
/**
* Load the rules from wiki documents.
*
* @param entity Any entity reference that is either a WIKI or a SPACE, or an entity containing a DOCUMENT entity.
* @return the access rules that could be loaded into the cache.
* @throws org.xwiki.security.authorization.AuthorizationException if an issue arise while reading these rules
* from the wiki.
*/
@Override
public SecurityRuleEntry read(SecurityReference entity) throws AuthorizationException
{
if (entity == null) {
return null;
}
if (entity.getOriginalReference() == null) {
// Public users (not logged in) are not stored anywhere and does not have their own rules
// More generally, any reference without a valid original reference should not be considered.
return new InternalSecurityRuleEntry(entity, Collections.<SecurityRule>emptyList());
}
DocumentReference documentReference;
DocumentReference classReference;
WikiReference wikiReference;
switch (entity.getType()) {
case WIKI:
wikiReference = new WikiReference(entity);
SpaceReference wikiSpace = new SpaceReference(XWikiConstants.XWIKI_SPACE, wikiReference);
documentReference = new DocumentReference(XWikiConstants.WIKI_DOC, wikiSpace);
classReference = new DocumentReference(XWikiConstants.GLOBAL_CLASSNAME, wikiSpace);
break;
case SPACE:
wikiReference = new WikiReference(entity.extractReference(EntityType.WIKI));
documentReference = new DocumentReference(XWikiConstants.SPACE_DOC, new SpaceReference(entity));
classReference = new DocumentReference(XWikiConstants.GLOBAL_CLASSNAME,
new SpaceReference(XWikiConstants.XWIKI_SPACE, wikiReference));
break;
case DOCUMENT:
wikiReference = new WikiReference(entity.extractReference(EntityType.WIKI));
documentReference = new DocumentReference(entity);
classReference = new DocumentReference(XWikiConstants.LOCAL_CLASSNAME,
new SpaceReference(XWikiConstants.XWIKI_SPACE, wikiReference));
break;
default:
throw new EntityTypeNotSupportedException(entity.getType(), this);
}
return new InternalSecurityRuleEntry(entity,
getSecurityRules(documentReference, classReference, wikiReference));
}
/**
* Get the document.
* @param documentReference reference to the document to be loaded.
* @return a list of matching base objects, or null if none where found.
* @throws AuthorizationException if an unexpected error occurs during retrieval.
*/
private XWikiDocument getDocument(DocumentReference documentReference) throws AuthorizationException
{
XWikiContext context = getXWikiContext();
try {
XWikiDocument doc = context.getWiki().getDocument(documentReference, context);
if (doc == null || doc.isNew()) {
return null;
}
return doc;
} catch (XWikiException e) {
throw new AuthorizationException(documentReference,
"Could not retrieve the document to check security access", e);
}
}
/**
* @param wikiReference the wiki to look for owner
* @return a reference to the owner of the wiki
* @throws AuthorizationException if the owner could not be retrieved.
*/
private DocumentReference getWikiOwner(WikiReference wikiReference) throws AuthorizationException
{
XWikiContext context = getXWikiContext();
String wikiOwner;
try {
wikiOwner = context.getWiki().getWikiOwner(wikiReference.getName(), context);
} catch (XWikiException e) {
throw new AuthorizationException(wikiReference,
"Could not retrieve the owner of this wiki", e);
}
if (wikiOwner == null) {
return null;
}
return resolver.resolve(wikiOwner, wikiReference);
}
/**
* Read right objects from an XWikiDocument and return them as XWikiSecurityRule.
* @param documentReference reference to document to read
* @param classReference reference to the right class to read
* @param wikiReference reference to the wiki of the document
* @return a collection of rules read from the document
* @throws AuthorizationException on error reading object from the document
*/
private Collection<SecurityRule> getSecurityRules(DocumentReference documentReference,
DocumentReference classReference, WikiReference wikiReference) throws AuthorizationException
{
boolean isGlobalRightsReference = isGlobalRightsReference(documentReference);
boolean isGlobalRightRequested = classReference.getName().equals(XWikiConstants.GLOBAL_CLASSNAME);
XWikiDocument doc = getDocument(documentReference);
// Get implied rules (creator, owner, global rights restriction)
List<SecurityRule> securityRules =
getImpliedRules(documentReference, doc, isGlobalRightsReference, isGlobalRightRequested);
if (doc == null) {
return securityRules;
}
// Convert existing rules on the entity
List<BaseObject> baseObjects = doc.getXObjects(classReference);
if (baseObjects != null) {
for (BaseObject obj : baseObjects) {
if (obj != null) {
SecurityRule rule;
try {
// Thanks to the resolver, the users and groups listed by the rights object, inherit
// the wiki from the document, unless explicitly given.
rule = XWikiSecurityRule.createNewRule(obj, resolver, wikiReference,
isGlobalRightsReference && !isGlobalRightRequested);
} catch (IllegalArgumentException e) {
// Do not add badly formed security rules.
continue;
}
securityRules.add(rule);
}
}
}
return securityRules;
}
/**
* Get rules implied by wiki owners, document creators, and global rights documents.
* @param documentReference reference to the document requested.
* @param document the document requested.
* @param isGlobalRightsReference true when the document is a document which host global rights.
* @param isGlobalRightRequested true when the request concern global rights.
* @return a list of implied security rules, or an empty list of there none.
* @throws AuthorizationException if anything goes wrong.
*/
private List<SecurityRule> getImpliedRules(DocumentReference documentReference, XWikiDocument document,
boolean isGlobalRightsReference, boolean isGlobalRightRequested) throws AuthorizationException
{
List<SecurityRule> rules = new ArrayList<SecurityRule>();
if (isGlobalRightsReference) {
if (isGlobalRightRequested) {
WikiReference documentWiki = documentReference.getWikiReference();
DocumentReference owner = getWikiOwner(documentWiki);
if (owner != null) {
XWikiContext context = getXWikiContext();
// Allow global rights to wiki owner
if (context.isMainWiki(documentWiki.getName())) {
rules.add(new XWikiSecurityRule(MAINWIKIOWNER_RIGHTS, RuleState.ALLOW, Collections.singleton(owner), null));
} else {
rules.add(new XWikiSecurityRule(OWNER_RIGHTS, RuleState.ALLOW, Collections.singleton(owner), null));
}
}
} else {
// Deny local edit right on documents hosting global rights for anyone but admins.
rules.add(DENY_EDIT);
}
}
if (!isGlobalRightRequested && document != null) {
DocumentReference creator = document.getCreatorReference();
// Allow local rights to document creator (unless it is a public creator)
if (creator != null && !XWikiConstants.GUEST_USER.equals(creator.getName())) {
rules.add(new XWikiSecurityRule(CREATOR_RIGHTS, RuleState.ALLOW, Collections.singleton(creator), null));
}
}
return rules;
}
/**
* Check if the entity reference refers to a document that may contain global rights objects. In other words
* '*:XWiki.XWikiPreferences' or '*:*.WebPreferences'.
*
* @param documentReference the document reference to check.
* @return true if the document is scanned for global rights objects during authorization.
*/
private boolean isGlobalRightsReference(DocumentReference documentReference) {
return (XWikiConstants.SPACE_DOC.equals(documentReference.getName())
|| (XWikiConstants.WIKI_DOC.equals(documentReference.getName())
&& XWikiConstants.XWIKI_SPACE.equals(documentReference.getParent().getName())));
}
}