blob: ea7e5add54a9d5a2248de16ed3b9c21306eb436d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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. 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 org.apache.blur.lucene.security.index;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.blur.lucene.security.DocumentAuthorizations;
import org.apache.blur.lucene.security.document.DocumentVisiblityField;
import org.apache.blur.lucene.security.search.BitSetDocumentVisibilityFilterCacheStrategy;
import org.apache.blur.lucene.security.search.DocumentVisibilityFilter;
import org.apache.blur.lucene.security.search.DocumentVisibilityFilterCacheStrategy;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.FloatField;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
public class FilterAccessControlFactory extends AccessControlFactory {
public static final String DISCOVER_FIELD = "_discover_";
public static final String READ_FIELD = "_read_";
public static final String READ_MASK_FIELD = "_readmask_";
public static final String READ_MASK_SUFFIX = "$" + READ_MASK_FIELD;
@Override
public String getDiscoverFieldName() {
return DISCOVER_FIELD;
}
@Override
public String getReadFieldName() {
return READ_FIELD;
}
@Override
public String getReadMaskFieldName() {
return READ_MASK_FIELD;
}
@Override
public String getReadMaskFieldSuffix() {
return READ_MASK_SUFFIX;
}
@Override
public AccessControlWriter getWriter() {
return new FilterAccessControlWriter();
}
@Override
public AccessControlReader getReader(Collection<String> readAuthorizations,
Collection<String> discoverAuthorizations, Set<String> discoverableFields, String defaultReadMaskMessage) {
return new FilterAccessControlReader(readAuthorizations, discoverAuthorizations, discoverableFields, defaultReadMaskMessage);
}
public static class FilterAccessControlReader extends AccessControlReader {
private final Set<String> _discoverableFields;
private final DocumentVisibilityFilter _readDocumentVisibilityFilter;
private final DocumentVisibilityFilter _discoverDocumentVisibilityFilter;
private final DocumentVisibilityFilterCacheStrategy _filterCacheStrategy;
private final String _defaultReadMaskMessage;
private Bits _readBits;
private Bits _discoverBits;
private boolean _noReadAccess;
private boolean _noDiscoverAccess;
private DocIdSet _readDocIdSet;
private DocIdSet _discoverDocIdSet;
private boolean _isClone;
public FilterAccessControlReader(Collection<String> readAuthorizations, Collection<String> discoverAuthorizations,
Set<String> discoverableFields, String defaultReadMaskMessage) {
this(readAuthorizations, discoverAuthorizations, discoverableFields,
BitSetDocumentVisibilityFilterCacheStrategy.INSTANCE, defaultReadMaskMessage);
}
public FilterAccessControlReader(Collection<String> readAuthorizations, Collection<String> discoverAuthorizations,
Set<String> discoverableFields, DocumentVisibilityFilterCacheStrategy filterCacheStrategy, String defaultReadMaskMessage) {
_defaultReadMaskMessage=defaultReadMaskMessage;
_filterCacheStrategy = filterCacheStrategy;
if (readAuthorizations == null || readAuthorizations.isEmpty()) {
_noReadAccess = true;
_readDocumentVisibilityFilter = null;
} else {
_readDocumentVisibilityFilter = new DocumentVisibilityFilter(READ_FIELD, new DocumentAuthorizations(
readAuthorizations), _filterCacheStrategy);
}
if (discoverAuthorizations == null || discoverAuthorizations.isEmpty()) {
_noDiscoverAccess = true;
_discoverDocumentVisibilityFilter = null;
} else {
_discoverDocumentVisibilityFilter = new DocumentVisibilityFilter(DISCOVER_FIELD, new DocumentAuthorizations(
discoverAuthorizations), _filterCacheStrategy);
}
_discoverableFields = discoverableFields;
}
@Override
protected boolean readAccess(int docID) throws IOException {
checkClone();
if (_noReadAccess) {
return false;
}
return _readBits.get(docID);
}
private void checkClone() throws IOException {
if (!_isClone) {
throw new IOException("No AtomicReader set.");
}
}
@Override
protected boolean discoverAccess(int docID) throws IOException {
checkClone();
if (_noDiscoverAccess) {
return false;
}
return _discoverBits.get(docID);
}
@Override
protected boolean readOrDiscoverAccess(int docID) throws IOException {
if (readAccess(docID)) {
return true;
} else {
return discoverAccess(docID);
}
}
@Override
public boolean canDiscoverField(String name) {
return _discoverableFields.contains(name);
}
@Override
public AccessControlReader clone(AtomicReader in) throws IOException {
try {
FilterAccessControlReader filterAccessControlReader = (FilterAccessControlReader) super.clone();
filterAccessControlReader._isClone = true;
if (_readDocumentVisibilityFilter == null) {
filterAccessControlReader._noReadAccess = true;
} else {
DocIdSet readDocIdSet = _readDocumentVisibilityFilter.getDocIdSet(in.getContext(), in.getLiveDocs());
if (readDocIdSet == DocIdSet.EMPTY_DOCIDSET || readDocIdSet == null) {
filterAccessControlReader._noReadAccess = true;
} else {
filterAccessControlReader._readBits = readDocIdSet.bits();
if (filterAccessControlReader._readBits == null) {
throw new IOException("Read Bits can not be null.");
}
}
filterAccessControlReader._readDocIdSet = readDocIdSet;
}
if (_discoverDocumentVisibilityFilter == null) {
filterAccessControlReader._noDiscoverAccess = true;
} else {
DocIdSet discoverDocIdSet = _discoverDocumentVisibilityFilter.getDocIdSet(in.getContext(), in.getLiveDocs());
if (discoverDocIdSet == DocIdSet.EMPTY_DOCIDSET || discoverDocIdSet == null) {
filterAccessControlReader._noDiscoverAccess = true;
} else {
filterAccessControlReader._discoverBits = discoverDocIdSet.bits();
if (filterAccessControlReader._discoverBits == null) {
throw new IOException("Read Bits can not be null.");
}
}
filterAccessControlReader._discoverDocIdSet = discoverDocIdSet;
}
return filterAccessControlReader;
} catch (CloneNotSupportedException e) {
throw new IOException(e);
}
}
@Override
public Filter getQueryFilter() throws IOException {
return new Filter() {
@Override
public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException {
FilterAccessControlReader accessControlReader = (FilterAccessControlReader) FilterAccessControlReader.this
.clone(context.reader());
DocIdSet secureDocIdSet = getSecureDocIdSet(accessControlReader);
if (acceptDocs == null) {
return secureDocIdSet;
} else {
return applyDeletes(acceptDocs, secureDocIdSet);
}
}
private DocIdSet getSecureDocIdSet(FilterAccessControlReader accessControlReader) throws IOException {
DocIdSet readDocIdSet = accessControlReader._readDocIdSet;
DocIdSet discoverDocIdSet = accessControlReader._discoverDocIdSet;
if (isEmptyOrNull(discoverDocIdSet) && isEmptyOrNull(readDocIdSet)) {
return DocIdSet.EMPTY_DOCIDSET;
} else if (isEmptyOrNull(discoverDocIdSet)) {
return readDocIdSet;
} else if (isEmptyOrNull(readDocIdSet)) {
return discoverDocIdSet;
} else {
return DocumentVisibilityFilter.getLogicalOr(readDocIdSet, discoverDocIdSet);
}
}
private boolean isEmptyOrNull(DocIdSet docIdSet) {
if (docIdSet == null || docIdSet == DocIdSet.EMPTY_DOCIDSET) {
return true;
}
return false;
}
};
}
protected DocIdSet applyDeletes(final Bits acceptDocs, final DocIdSet secureDocIdSet) {
return new DocIdSet() {
@Override
public DocIdSetIterator iterator() throws IOException {
final DocIdSetIterator docIdSetIterator = secureDocIdSet.iterator();
return new DocIdSetIterator() {
@Override
public int nextDoc() throws IOException {
int docId;
while ((docId = docIdSetIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (acceptDocs.get(docId)) {
return docId;
}
}
return DocIdSetIterator.NO_MORE_DOCS;
}
@Override
public int advance(int target) throws IOException {
int docId = docIdSetIterator.advance(target);
if (docId == DocIdSetIterator.NO_MORE_DOCS) {
return DocIdSetIterator.NO_MORE_DOCS;
}
if (acceptDocs.get(docId)) {
return docId;
}
while ((docId = docIdSetIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (acceptDocs.get(docId)) {
return docId;
}
}
return DocIdSetIterator.NO_MORE_DOCS;
}
@Override
public int docID() {
return docIdSetIterator.docID();
}
@Override
public long cost() {
return docIdSetIterator.cost() + 1;
}
};
}
};
}
@Override
public String getDefaultReadMaskMessage() {
return _defaultReadMaskMessage;
}
}
public static class FilterAccessControlWriter extends AccessControlWriter {
@Override
public Iterable<? extends IndexableField> addReadVisiblity(String read, Iterable<? extends IndexableField> fields) {
return addField(fields, new DocumentVisiblityField(READ_FIELD, read, Store.YES));
}
@Override
public Iterable<? extends IndexableField> addDiscoverVisiblity(String discover, Iterable<? extends IndexableField> fields) {
return addField(fields, new DocumentVisiblityField(DISCOVER_FIELD, discover, Store.YES));
}
@Override
public Iterable<? extends IndexableField> addReadMask(String fieldToMask, Iterable<? extends IndexableField> fields) {
return addField(fields, new StoredField(READ_MASK_FIELD, fieldToMask));
}
@Override
public Iterable<? extends IndexableField> lastStepBeforeIndexing(Iterable<? extends IndexableField> fields) {
return processFieldMasks(fields);
}
public static Iterable<? extends IndexableField> processFieldMasks(Iterable<? extends IndexableField> fields) {
Set<String> fieldsToMask = getFieldsToMask(fields);
if (fieldsToMask.isEmpty()) {
return fields;
}
List<IndexableField> result = new ArrayList<IndexableField>();
for (IndexableField field : fields) {
// If field is to be indexed and is to be read masked.
if (fieldsToMask.contains(field.name())) {
// If field is a doc value, then don't bother indexing.
if (!isDocValue(field)) {
if (isStoredField(field)) {
// Stored fields are not indexed, and the document fetch check
// handles the mask.
result.add(field);
} else {
IndexableField mask = createMaskField(field);
result.add(field);
result.add(mask);
}
}
} else {
result.add(field);
}
}
return result;
}
private static Set<String> getFieldsToMask(Iterable<? extends IndexableField> fields) {
Set<String> result = new HashSet<String>();
for (IndexableField field : fields) {
if (field.name().equals(READ_MASK_FIELD)) {
result.add(getFieldNameOnly(field.stringValue()));
}
}
return result;
}
private static String getFieldNameOnly(String s) {
// remove any stored messages
int indexOf = s.indexOf('|');
if (indexOf < 0) {
return s;
} else {
return s.substring(0, indexOf);
}
}
private static boolean isStoredField(IndexableField field) {
return !field.fieldType().indexed();
}
private static IndexableField createMaskField(IndexableField field) {
FieldType fieldTypeNotStored = getFieldTypeNotStored(field);
String name = field.name() + READ_MASK_SUFFIX;
if (field instanceof DoubleField) {
DoubleField f = (DoubleField) field;
return new DoubleField(name, (double) f.numericValue(), fieldTypeNotStored);
} else if (field instanceof FloatField) {
FloatField f = (FloatField) field;
return new FloatField(name, (float) f.numericValue(), fieldTypeNotStored);
} else if (field instanceof IntField) {
IntField f = (IntField) field;
return new IntField(name, (int) f.numericValue(), fieldTypeNotStored);
} else if (field instanceof LongField) {
LongField f = (LongField) field;
return new LongField(name, (long) f.numericValue(), fieldTypeNotStored);
} else if (field instanceof StringField) {
StringField f = (StringField) field;
return new StringField(name, f.stringValue(), Store.NO);
} else if (field instanceof StringField) {
TextField f = (TextField) field;
Reader readerValue = f.readerValue();
if (readerValue != null) {
return new TextField(name, readerValue);
}
TokenStream tokenStreamValue = f.tokenStreamValue();
if (tokenStreamValue != null) {
return new TextField(name, tokenStreamValue);
}
return new TextField(name, f.stringValue(), Store.NO);
} else if (field.getClass().equals(Field.class)) {
Field f = (Field) field;
String stringValue = f.stringValue();
if (stringValue != null) {
return new Field(name, stringValue, fieldTypeNotStored);
}
BytesRef binaryValue = f.binaryValue();
if (binaryValue != null) {
return new Field(name, binaryValue, fieldTypeNotStored);
}
Number numericValue = f.numericValue();
if (numericValue != null) {
throw new RuntimeException("Field [" + field + "] with type [" + field.getClass() + "] is not supported.");
}
Reader readerValue = f.readerValue();
if (readerValue != null) {
return new Field(name, readerValue, fieldTypeNotStored);
}
TokenStream tokenStreamValue = f.tokenStreamValue();
if (tokenStreamValue != null) {
return new Field(name, tokenStreamValue, fieldTypeNotStored);
}
throw new RuntimeException("Field [" + field + "] with type [" + field.getClass() + "] is not supported.");
} else {
throw new RuntimeException("Field [" + field + "] with type [" + field.getClass() + "] is not supported.");
}
}
private static FieldType getFieldTypeNotStored(IndexableField indexableField) {
Field field = (Field) indexableField;
FieldType fieldType = field.fieldType();
FieldType result = new FieldType(fieldType);
result.setStored(false);
result.freeze();
return result;
}
private static boolean isDocValue(IndexableField field) {
if (field instanceof BinaryDocValuesField) {
return true;
} else if (field instanceof NumericDocValuesField) {
return true;
} else if (field instanceof SortedDocValuesField) {
return true;
} else if (field instanceof SortedSetDocValuesField) {
return true;
} else {
return false;
}
}
}
}