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