001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.access;
019
020import java.io.IOException;
021import java.util.Map;
022import java.util.Objects;
023import org.apache.hadoop.hbase.Cell;
024import org.apache.hadoop.hbase.CellUtil;
025import org.apache.hadoop.hbase.ExtendedCell;
026import org.apache.hadoop.hbase.PrivateCellUtil;
027import org.apache.hadoop.hbase.TableName;
028import org.apache.hadoop.hbase.exceptions.DeserializationException;
029import org.apache.hadoop.hbase.filter.FilterBase;
030import org.apache.hadoop.hbase.security.User;
031import org.apache.hadoop.hbase.util.ByteRange;
032import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
033import org.apache.yetus.audience.InterfaceAudience;
034
035/**
036 * <strong>NOTE: for internal use only by AccessController implementation</strong>
037 * <p>
038 * TODO: There is room for further performance optimization here. Calling AuthManager.authorize()
039 * per KeyValue imposes a fair amount of overhead. A more optimized solution might look at the
040 * qualifiers where permissions are actually granted and explicitly limit the scan to those.
041 * </p>
042 * <p>
043 * We should aim to use this _only_ when access to the requested column families is not granted at
044 * the column family levels. If table or column family access succeeds, then there is no need to
045 * impose the overhead of this filter.
046 * </p>
047 */
048@InterfaceAudience.Private
049class AccessControlFilter extends FilterBase {
050
051  public static enum Strategy {
052    /** Filter only by checking the table or CF permissions */
053    CHECK_TABLE_AND_CF_ONLY,
054    /** Cell permissions can override table or CF permissions */
055    CHECK_CELL_DEFAULT,
056  }
057
058  private AuthManager authManager;
059  private TableName table;
060  private User user;
061  private boolean isSystemTable;
062  private Strategy strategy;
063  private Map<ByteRange, Integer> cfVsMaxVersions;
064  private int familyMaxVersions;
065  private int currentVersions;
066  private ByteRange prevFam;
067  private ByteRange prevQual;
068
069  /**
070   * For Writable
071   */
072  AccessControlFilter() {
073  }
074
075  AccessControlFilter(AuthManager mgr, User ugi, TableName tableName, Strategy strategy,
076    Map<ByteRange, Integer> cfVsMaxVersions) {
077    authManager = mgr;
078    table = tableName;
079    user = ugi;
080    isSystemTable = tableName.isSystemTable();
081    this.strategy = strategy;
082    this.cfVsMaxVersions = cfVsMaxVersions;
083    this.prevFam = new SimpleMutableByteRange();
084    this.prevQual = new SimpleMutableByteRange();
085  }
086
087  @Override
088  public boolean filterRowKey(Cell cell) throws IOException {
089    // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
090    return false;
091  }
092
093  @Override
094  public ReturnCode filterCell(final Cell cell) {
095    if (isSystemTable) {
096      return ReturnCode.INCLUDE;
097    }
098    if (
099      prevFam.getBytes() == null || !(PrivateCellUtil.matchingFamily(cell, prevFam.getBytes(),
100        prevFam.getOffset(), prevFam.getLength()))
101    ) {
102      prevFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
103      // Similar to VisibilityLabelFilter
104      familyMaxVersions = cfVsMaxVersions.get(prevFam);
105      // Family is changed. Just unset curQualifier.
106      prevQual.unset();
107    }
108    if (
109      prevQual.getBytes() == null || !(PrivateCellUtil.matchingQualifier(cell, prevQual.getBytes(),
110        prevQual.getOffset(), prevQual.getLength()))
111    ) {
112      prevQual.set(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
113      currentVersions = 0;
114    }
115    currentVersions++;
116    if (currentVersions > familyMaxVersions) {
117      return ReturnCode.SKIP;
118    }
119    // XXX: Compare in place, don't clone
120    byte[] f = CellUtil.cloneFamily(cell);
121    byte[] q = CellUtil.cloneQualifier(cell);
122    switch (strategy) {
123      // Filter only by checking the table or CF permissions
124      case CHECK_TABLE_AND_CF_ONLY: {
125        if (authManager.authorizeUserTable(user, table, f, q, Permission.Action.READ)) {
126          return ReturnCode.INCLUDE;
127        }
128      }
129        break;
130      // Cell permissions can override table or CF permissions
131      case CHECK_CELL_DEFAULT: {
132        if (
133          authManager.authorizeUserTable(user, table, f, q, Permission.Action.READ)
134            || authManager.authorizeCell(user, table, (ExtendedCell) cell, Permission.Action.READ)
135        ) {
136          return ReturnCode.INCLUDE;
137        }
138      }
139        break;
140      default:
141        throw new RuntimeException("Unhandled strategy " + strategy);
142    }
143
144    return ReturnCode.SKIP;
145  }
146
147  @Override
148  public void reset() throws IOException {
149    this.prevFam.unset();
150    this.prevQual.unset();
151    this.familyMaxVersions = 0;
152    this.currentVersions = 0;
153  }
154
155  /** Returns The filter serialized using pb */
156  @Override
157  public byte[] toByteArray() {
158    // no implementation, server-side use only
159    throw new UnsupportedOperationException(
160      "Serialization not supported.  Intended for server-side use only.");
161  }
162
163  /**
164   * @param pbBytes A pb serialized {@link AccessControlFilter} instance
165   * @return An instance of {@link AccessControlFilter} made from <code>bytes</code>
166   * @throws org.apache.hadoop.hbase.exceptions.DeserializationException
167   * @see #toByteArray()
168   */
169  public static AccessControlFilter parseFrom(final byte[] pbBytes)
170    throws DeserializationException {
171    // no implementation, server-side use only
172    throw new UnsupportedOperationException(
173      "Serialization not supported.  Intended for server-side use only.");
174  }
175
176  @Override
177  public boolean equals(Object obj) {
178    if (!(obj instanceof AccessControlFilter)) {
179      return false;
180    }
181    if (this == obj) {
182      return true;
183    }
184    AccessControlFilter f = (AccessControlFilter) obj;
185    return this.authManager.equals(f.authManager) && this.table.equals(f.table)
186      && this.user.equals(f.user) && this.strategy.equals(f.strategy)
187      && this.cfVsMaxVersions.equals(f.cfVsMaxVersions);
188  }
189
190  @Override
191  public int hashCode() {
192    return Objects.hash(this.authManager, this.table, this.strategy, this.user,
193      this.cfVsMaxVersions);
194  }
195}