/** * 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; } } } }