View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.security.visibility;
20  
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Set;
28  
29  import org.apache.hadoop.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.Cell;
31  import org.apache.hadoop.hbase.KeyValue;
32  import org.apache.hadoop.hbase.KeyValue.Type;
33  import org.apache.hadoop.hbase.Tag;
34  import org.apache.hadoop.hbase.regionserver.ScanDeleteTracker;
35  import org.apache.hadoop.hbase.util.Bytes;
36  
37  /**
38   * Similar to ScanDeletTracker but tracks the visibility expression also before
39   * deciding if a Cell can be considered deleted
40   */
41  @InterfaceAudience.Private
42  public class VisibilityScanDeleteTracker extends ScanDeleteTracker {
43  
44    // Its better to track the visibility tags in delete based on each type.  Create individual
45    // data structures for tracking each of them.  This would ensure that there is no tracking based
46    // on time and also would handle all cases where deletefamily or deletecolumns is specified with
47    // Latest_timestamp.  In such cases the ts in the delete marker and the masking
48    // put will not be same. So going with individual data structures for different delete
49    // type would solve this problem and also ensure that the combination of different type
50    // of deletes with diff ts would also work fine
51    // Track per TS
52    private Map<Long, List<Tag>> visibilityTagsDeleteFamily = new HashMap<Long, List<Tag>>();
53    // Delete family version with different ts and different visibility expression could come.
54    // Need to track it per ts.
55    private Map<Long,List<Tag>> visibilityTagsDeleteFamilyVersion = new HashMap<Long, List<Tag>>();
56    private List<List<Tag>> visibilityTagsDeleteColumns;
57    // Tracking as List<List> is to handle same ts cell but different visibility tag. 
58    // TODO : Need to handle puts with same ts but different vis tags.
59    private List<List<Tag>> visiblityTagsDeleteColumnVersion = new ArrayList<List<Tag>>();
60  
61    public VisibilityScanDeleteTracker() {
62      super();
63    }
64  
65    @Override
66    public void add(Cell delCell) {
67      //Cannot call super.add because need to find if the delete needs to be considered
68      long timestamp = delCell.getTimestamp();
69      int qualifierOffset = delCell.getQualifierOffset();
70      int qualifierLength = delCell.getQualifierLength();
71      byte type = delCell.getTypeByte();
72      if (type == KeyValue.Type.DeleteFamily.getCode()) {
73        hasFamilyStamp = true;
74        //familyStamps.add(delCell.getTimestamp());
75        extractDeleteTags(delCell, KeyValue.Type.DeleteFamily);
76        return;
77      } else if (type == KeyValue.Type.DeleteFamilyVersion.getCode()) {
78        familyVersionStamps.add(timestamp);
79        extractDeleteTags(delCell, KeyValue.Type.DeleteFamilyVersion);
80        return;
81      }
82      // new column, or more general delete type
83      if (deleteBuffer != null) {
84        if (Bytes.compareTo(deleteBuffer, deleteOffset, deleteLength, delCell.getQualifierArray(),
85            qualifierOffset, qualifierLength) != 0) {
86          // A case where there are deletes for a column qualifier but there are
87          // no corresponding puts for them. Rare case.
88          visibilityTagsDeleteColumns = null;
89          visiblityTagsDeleteColumnVersion = null;
90        } else if (type == KeyValue.Type.Delete.getCode() && (deleteTimestamp != timestamp)) {
91          // there is a timestamp change which means we could clear the list
92          // when ts is same and the vis tags are different we need to collect
93          // them all. Interesting part is that in the normal case of puts if
94          // there are 2 cells with same ts and diff vis tags only one of them is
95          // returned. Handling with a single List<Tag> would mean that only one
96          // of the cell would be considered. Doing this as a precaution.
97          // Rare cases.
98          visiblityTagsDeleteColumnVersion = null;
99        }
100     }
101     deleteBuffer = delCell.getQualifierArray();
102     deleteOffset = qualifierOffset;
103     deleteLength = qualifierLength;
104     deleteType = type;
105     deleteTimestamp = timestamp;
106     extractDeleteTags(delCell, KeyValue.Type.codeToType(type));
107   }
108 
109   private void extractDeleteTags(Cell delCell, Type type) {
110     // If tag is present in the delete
111     if (delCell.getTagsLengthUnsigned() > 0) {
112       switch (type) {
113         case DeleteFamily:
114           List<Tag> delTags = new ArrayList<Tag>();
115           if (visibilityTagsDeleteFamily != null) {
116             VisibilityUtils.getVisibilityTags(delCell, delTags);
117             if (!delTags.isEmpty()) {
118               visibilityTagsDeleteFamily.put(delCell.getTimestamp(), delTags);
119             }
120           }
121           break;
122         case DeleteFamilyVersion:
123           delTags = new ArrayList<Tag>();
124           VisibilityUtils.getVisibilityTags(delCell, delTags);
125           if (!delTags.isEmpty()) {
126             visibilityTagsDeleteFamilyVersion.put(delCell.getTimestamp(), delTags);
127           }
128           break;
129         case DeleteColumn:
130           if (visibilityTagsDeleteColumns == null) {
131             visibilityTagsDeleteColumns = new ArrayList<List<Tag>>();
132           }
133           delTags = new ArrayList<Tag>();
134           VisibilityUtils.getVisibilityTags(delCell, delTags);
135           if (!delTags.isEmpty()) {
136             visibilityTagsDeleteColumns.add(delTags);
137           }
138           break;
139         case Delete:
140           if (visiblityTagsDeleteColumnVersion == null) {
141             visiblityTagsDeleteColumnVersion = new ArrayList<List<Tag>>();
142           }
143           delTags = new ArrayList<Tag>();
144           VisibilityUtils.getVisibilityTags(delCell, delTags);
145           if (!delTags.isEmpty()) {
146             visiblityTagsDeleteColumnVersion.add(delTags);
147           }
148           break;
149         default:
150           throw new IllegalArgumentException("Invalid delete type");
151       }
152     } else {
153       switch (type) {
154         case DeleteFamily:
155           visibilityTagsDeleteFamily = null;
156           break;
157         case DeleteFamilyVersion:
158           visibilityTagsDeleteFamilyVersion = null;
159           break;
160         case DeleteColumn:
161           visibilityTagsDeleteColumns = null;
162           break;
163         case Delete:
164           visiblityTagsDeleteColumnVersion = null;
165           break;
166         default:
167           throw new IllegalArgumentException("Invalid delete type");
168       }
169     }
170   }
171 
172   @Override
173   public DeleteResult isDeleted(Cell cell) {
174     long timestamp = cell.getTimestamp();
175     int qualifierOffset = cell.getQualifierOffset();
176     int qualifierLength = cell.getQualifierLength();
177     if (hasFamilyStamp) {
178       if (visibilityTagsDeleteFamily != null) {
179         Set<Entry<Long, List<Tag>>> deleteFamilies = visibilityTagsDeleteFamily.entrySet();
180         Iterator<Entry<Long, List<Tag>>> iterator = deleteFamilies.iterator();
181         while (iterator.hasNext()) {
182           Entry<Long, List<Tag>> entry = iterator.next();
183           if (timestamp <= entry.getKey()) {
184             boolean matchFound = VisibilityUtils.checkForMatchingVisibilityTags(cell,
185                 entry.getValue());
186             if (matchFound) {
187               return DeleteResult.FAMILY_VERSION_DELETED;
188             }
189           }
190         }
191       } else {
192         if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
193           // No tags
194           return DeleteResult.FAMILY_VERSION_DELETED;
195         }
196       }
197     }
198     if (familyVersionStamps.contains(Long.valueOf(timestamp))) {
199       if (visibilityTagsDeleteFamilyVersion != null) {
200         List<Tag> tags = visibilityTagsDeleteFamilyVersion.get(Long.valueOf(timestamp));
201         if (tags != null) {
202           boolean matchFound = VisibilityUtils.checkForMatchingVisibilityTags(cell, tags);
203           if (matchFound) {
204             return DeleteResult.FAMILY_VERSION_DELETED;
205           }
206         }
207       } else {
208         if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
209           // No tags
210           return DeleteResult.FAMILY_VERSION_DELETED;
211         }
212       }
213     }
214     if (deleteBuffer != null) {
215       int ret = Bytes.compareTo(deleteBuffer, deleteOffset, deleteLength, cell.getQualifierArray(),
216           qualifierOffset, qualifierLength);
217 
218       if (ret == 0) {
219         if (deleteType == KeyValue.Type.DeleteColumn.getCode()) {
220           if (visibilityTagsDeleteColumns != null) {
221             for (List<Tag> tags : visibilityTagsDeleteColumns) {
222               boolean matchFound = VisibilityUtils.checkForMatchingVisibilityTags(cell,
223                   tags);
224               if (matchFound) {
225                 return DeleteResult.VERSION_DELETED;
226               }
227             }
228           } else {
229             if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
230               // No tags
231               return DeleteResult.VERSION_DELETED;
232             }
233           }
234         }
235         // Delete (aka DeleteVersion)
236         // If the timestamp is the same, keep this one
237         if (timestamp == deleteTimestamp) {
238           if (visiblityTagsDeleteColumnVersion != null) {
239             for (List<Tag> tags : visiblityTagsDeleteColumnVersion) {
240               boolean matchFound = VisibilityUtils.checkForMatchingVisibilityTags(cell,
241                   tags);
242               if (matchFound) {
243                 return DeleteResult.VERSION_DELETED;
244               }
245             }
246           } else {
247             if (!VisibilityUtils.isVisibilityTagsPresent(cell)) {
248               // No tags
249               return DeleteResult.VERSION_DELETED;
250             }
251           }
252         }
253       } else if (ret < 0) {
254         // Next column case.
255         deleteBuffer = null;
256         visibilityTagsDeleteColumns = null;
257         visiblityTagsDeleteColumnVersion = null;
258       } else {
259         throw new IllegalStateException("isDeleted failed: deleteBuffer="
260             + Bytes.toStringBinary(deleteBuffer, deleteOffset, deleteLength) + ", qualifier="
261             + Bytes.toStringBinary(cell.getQualifierArray(), qualifierOffset, qualifierLength)
262             + ", timestamp=" + timestamp + ", comparison result: " + ret);
263       }
264     }
265     return DeleteResult.NOT_DELETED;
266   }
267 
268   @Override
269   public void reset() {
270     super.reset();
271     visibilityTagsDeleteColumns = null;
272     visibilityTagsDeleteFamily = new HashMap<Long, List<Tag>>();
273     visibilityTagsDeleteFamilyVersion = new HashMap<Long, List<Tag>>();
274     visiblityTagsDeleteColumnVersion = null;
275   }
276 }