// Copyright 2012 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // 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 com.google.enterprise.connector.spi; import com.google.enterprise.connector.spi.SpiConstants.AclAccess; import com.google.enterprise.connector.spi.SpiConstants.AclInheritanceType; import com.google.enterprise.connector.spi.SpiConstants.AclScope; import com.google.enterprise.connector.spi.SpiConstants.DocumentType; import com.google.enterprise.connector.spi.SpiConstants.FeedType; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * An implementation of the {@link Document} interface for use with * ACLs. Instances are created using the static factory methods. There * are no public constructors. Implementors may use this directly or * for reference. * * @since 3.0 */ public class SecureDocument implements Document { protected static final List<Value> ACL_VALUE = getValueList(DocumentType.ACL.toString()); protected static final List<Value> RECORD_VALUE = getValueList(DocumentType.RECORD.toString()); protected static List<Value> getValueList(String value) { return Collections.singletonList(Value.getStringValue(value)); } /** An empty, immutable document with no properties. */ protected static class EmptyDocument implements Document { @Override public Property findProperty(String name) { return null; } @Override public Set<String> getPropertyNames() { return Collections.emptySet(); } } /** * Constructs a {@code SecureDocument} representing a stand-alone ACL. * Implies a document type of {@code ACL}. * * @param docId the unique document ID * @param searchUrl the repository URL, may be {@code null} * @see SpiConstants#PROPNAME_DOCID * @see SpiConstants#PROPNAME_SEARCHURL */ public static SecureDocument createAcl(String docId, String searchUrl) { Map<String, List<Value>> copy = new HashMap<String, List<Value>>(); copy.put(SpiConstants.PROPNAME_DOCID, getValueList(docId)); if (searchUrl != null) { copy.put(SpiConstants.PROPNAME_SEARCHURL, getValueList(searchUrl)); } copy.put(SpiConstants.PROPNAME_DOCUMENTTYPE, ACL_VALUE); return new SecureDocument(copy, new EmptyDocument()); } /** * Constructs a {@code SecureDocument} representing a stand-alone ACL whose * metadata consists of the supplied {@code Map} of properties, * associating property names with their {@link Value Values}. * Implies a document type of {@code ACL}. * * @param properties a {@code Map} of document metadata */ public static SecureDocument createAcl(Map<String, List<Value>> properties) { Map<String, List<Value>> copy = new HashMap<String, List<Value>>(properties); copy.put(SpiConstants.PROPNAME_DOCUMENTTYPE, ACL_VALUE); return new SecureDocument(copy, new EmptyDocument()); } /** * Constructs a {@code SecureDocument} representing a document with * an ACL whose metadata consists of the properties of the given * {@code Document}. The document may represent a stand-alone ACL or * an indexable document. * * @param document a base document whose properties are included in * the return {@code SecureDocument} * @throws RepositoryException if an error occurs accessing the * underlying document */ public static SecureDocument createDocumentWithAcl(Document document) throws RepositoryException { Map<String, List<Value>> empty = new HashMap<String, List<Value>>(); return new SecureDocument(empty, document); } /** * Constructs a {@code SecureDocument} representing a document with * an ACL whose metadata consists of the supplied {@code Map} of * properties, associating property names with their {@link Value * Values}. Implies a document type of {@code RECORD}. * * @param properties a {@code Map} of document metadata */ public static SecureDocument createDocumentWithAcl( Map<String, List<Value>> properties) { Map<String, List<Value>> copy = new HashMap<String, List<Value>>(properties); copy.put(SpiConstants.PROPNAME_DOCUMENTTYPE, RECORD_VALUE); return new SecureDocument(copy, new EmptyDocument()); } /** Base document. */ protected final Document document; /** Additional properties, but not necessarily strictly ACL properties. */ protected final Map<String, List<Value>> properties; /** * Constucts a {@code SimpleDocument} whose metadata consists * of the supplied {@code Map} of {@code properties}, associating * property names with their {@link Value Values}, together with * the properties of the base document. * * @param properties a non-null, mutable {@code Map} of document metadata * @param document a non-null underlying document * @throws RepositoryException if an error occurs accessing the * underlying document */ protected SecureDocument(Map<String, List<Value>> properties, Document document) { this.properties = properties; this.document = document; } /** * {@inheritDoc} * * @throws RepositoryException if an error occurs accessing the * underlying document */ @Override public Property findProperty(String name) throws RepositoryException { List<Value> list = properties.get(name); return (list == null) ? document.findProperty(name) : new SimpleProperty(list); } /** * {@inheritDoc} * * @throws RepositoryException if an error occurs accessing the * underlying document */ @Override public Set<String> getPropertyNames() throws RepositoryException { Set<String> combined = new HashSet<String>(); combined.addAll(properties.keySet()); combined.addAll(document.getPropertyNames()); return combined; } /** * Sets the URL for an inherited ACL. * <p> * Only one of {@link #setInheritFrom(String url)} or * {@link #setInheritFrom(String docid, SpiConstants.FeedType feedType)} * should be called. If both are called the {@code inherit-from} * {@code docid} and {@code feedType} will be ignored in favor * of the {@code inherit-from url}. * * @param url the URL of the parent ACL * @see SpiConstants#PROPNAME_ACLINHERITFROM */ public void setInheritFrom(String url) { properties.put(SpiConstants.PROPNAME_ACLINHERITFROM, getValueList(url)); // Obscure these properties, in case they are specified in the delegate. properties.put(SpiConstants.PROPNAME_ACLINHERITFROM_DOCID, null); properties.put(SpiConstants.PROPNAME_ACLINHERITFROM_FEEDTYPE, null); } /** * Sets the components needed to construct the inherit-from URL of an ACL * that was fed using FeedType.CONTENT, FeedType.CONTENTURL, or FeedType.ACL. * <p> * Only one of {@link #setInheritFrom(String url)} or * {@link #setInheritFrom(String docid, SpiConstants.FeedType feedType)} * should be called. If both are called the {@code inherit-from} * {@code docid} and {@code feedType} will be ignored in favor * of the {@code inherit-from url}. * * @param docid the docid of the parent ACL document * @param feedType the FeedType of the parent ACL document, or {@code null} * if this document's FeedType should be used. * @see SpiConstants#PROPNAME_ACLINHERITFROM */ public void setInheritFrom(String docid, FeedType feedType) { properties.put(SpiConstants.PROPNAME_ACLINHERITFROM_DOCID, getValueList(docid)); if (feedType != null) { properties.put(SpiConstants.PROPNAME_ACLINHERITFROM_FEEDTYPE, getValueList(feedType.toString())); } // Obscure this property, in case it is specified in the delegate. properties.put(SpiConstants.PROPNAME_ACLINHERITFROM, null); } /** * Sets the inheritance type. * * @param type one of the {@link AclInheritanceType} values * @see SpiConstants#PROPNAME_ACLINHERITANCETYPE */ public void setInheritanceType(AclInheritanceType type) { properties.put(SpiConstants.PROPNAME_ACLINHERITANCETYPE, getValueList(type.toString())); } /** * Adds a user or group to the ACL. The scope determines whether the * principal is a user or group, and the access determines whether * the principal is permitted or denied access. * * @param name the user or group name * @param scope whether the principal is a user or group name, one * of the {@link AclScope} values * @param access whether access is permitted or denied, one of the * {@link AclAccess} values * @throws RepositoryException if an error occurs accessing the * underlying document * @see SpiConstants#PROPNAME_ACLGROUPS * @see SpiConstants#PROPNAME_ACLUSERS * @see SpiConstants#PROPNAME_ACLDENYGROUPS * @see SpiConstants#PROPNAME_ACLDENYUSERS */ public void addPrincipal(String name, AclScope scope, AclAccess access) throws RepositoryException { String propertyName; if (scope == AclScope.USER && access == AclAccess.PERMIT) { propertyName = SpiConstants.PROPNAME_ACLUSERS; } else if (scope == AclScope.GROUP && access == AclAccess.PERMIT) { propertyName = SpiConstants.PROPNAME_ACLGROUPS; } else if (scope == AclScope.USER && access == AclAccess.DENY) { propertyName = SpiConstants.PROPNAME_ACLDENYUSERS; } else if (scope == AclScope.GROUP && access == AclAccess.DENY) { propertyName = SpiConstants.PROPNAME_ACLDENYGROUPS; } else { throw new AssertionError("Unknown scope " + scope + " or access " + access); } List<Value> list = properties.get(propertyName); if (list == null) { list = new ArrayList<Value>(); properties.put(propertyName, list); Property existing = document.findProperty(propertyName); if (existing != null) { Value value; while ((value = existing.nextValue()) != null) { list.add(value); } } } list.add(Value.getStringValue(name)); } }