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