View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security.visibility;
19  
20  import static org.apache.hadoop.hbase.TagType.VISIBILITY_TAG_TYPE;
21  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
22  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
23  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABEL_QUALIFIER;
24  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT;
25  import static org.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL;
26  
27  import java.io.ByteArrayOutputStream;
28  import java.io.DataOutputStream;
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.BitSet;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.regex.Pattern;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.hadoop.conf.Configuration;
44  import org.apache.hadoop.hbase.Cell;
45  import org.apache.hadoop.hbase.CellUtil;
46  import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
47  import org.apache.hadoop.hbase.Tag;
48  import org.apache.hadoop.hbase.TagType;
49  import org.apache.hadoop.hbase.classification.InterfaceAudience;
50  import org.apache.hadoop.hbase.client.Delete;
51  import org.apache.hadoop.hbase.client.Mutation;
52  import org.apache.hadoop.hbase.client.Put;
53  import org.apache.hadoop.hbase.client.Scan;
54  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
55  import org.apache.hadoop.hbase.filter.Filter;
56  import org.apache.hadoop.hbase.io.util.StreamUtils;
57  import org.apache.hadoop.hbase.regionserver.HRegion;
58  import org.apache.hadoop.hbase.regionserver.OperationStatus;
59  import org.apache.hadoop.hbase.regionserver.RegionScanner;
60  import org.apache.hadoop.hbase.security.User;
61  import org.apache.hadoop.hbase.security.access.AccessControlLists;
62  import org.apache.hadoop.hbase.util.Bytes;
63  import org.apache.hadoop.hbase.util.Pair;
64  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
65  
66  @InterfaceAudience.Private
67  public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService {
68  
69    private static final Log LOG = LogFactory.getLog(DefaultVisibilityLabelServiceImpl.class);
70  
71    // "system" label is having an ordinal value 1.
72    private static final int SYSTEM_LABEL_ORDINAL = 1;
73    private static final Tag[] LABELS_TABLE_TAGS = new Tag[1];
74    private static final byte[] DUMMY_VALUE = new byte[0];
75  
76    private volatile int ordinalCounter = -1;
77    private Configuration conf;
78    private HRegion labelsRegion;
79    private VisibilityLabelsCache labelsCache;
80    private List<ScanLabelGenerator> scanLabelGenerators;
81    private List<String> superUsers;
82    private List<String> superGroups;
83  
84    static {
85      ByteArrayOutputStream baos = new ByteArrayOutputStream();
86      DataOutputStream dos = new DataOutputStream(baos);
87      try {
88        StreamUtils.writeRawVInt32(dos, SYSTEM_LABEL_ORDINAL);
89      } catch (IOException e) {
90        // We write to a byte array. No Exception can happen.
91      }
92      LABELS_TABLE_TAGS[0] = new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray());
93    }
94  
95    public DefaultVisibilityLabelServiceImpl() {
96  
97    }
98  
99    @Override
100   public void setConf(Configuration conf) {
101     this.conf = conf;
102   }
103 
104   @Override
105   public Configuration getConf() {
106     return this.conf;
107   }
108 
109   @Override
110   public void init(RegionCoprocessorEnvironment e) throws IOException {
111     ZooKeeperWatcher zk = e.getRegionServerServices().getZooKeeper();
112     try {
113       labelsCache = VisibilityLabelsCache.createAndGet(zk, this.conf);
114     } catch (IOException ioe) {
115       LOG.error("Error creating VisibilityLabelsCache", ioe);
116       throw ioe;
117     }
118     this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
119     Pair<List<String>, List<String>> superUsersAndGroups =
120         VisibilityUtils.getSystemAndSuperUsers(this.conf);
121     this.superUsers = superUsersAndGroups.getFirst();
122     this.superGroups = superUsersAndGroups.getSecond();
123     if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
124       this.labelsRegion = e.getRegion();
125       Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
126           extractLabelsAndAuths(getExistingLabelsWithAuths());
127       Map<String, Integer> labels = labelsAndUserAuths.getFirst();
128       Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
129       // Add the "system" label if it is not added into the system yet
130       addSystemLabel(this.labelsRegion, labels, userAuths);
131       int ordinal = SYSTEM_LABEL_ORDINAL; // Ordinal 1 is reserved for "system" label.
132       for (Integer i : labels.values()) {
133         if (i > ordinal) {
134           ordinal = i;
135         }
136       }
137       this.ordinalCounter = ordinal + 1;
138       if (labels.size() > 0) {
139         // If there is no data need not write to zk
140         byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(labels);
141         this.labelsCache.writeToZookeeper(serialized, true);
142         this.labelsCache.refreshLabelsCache(serialized);
143       }
144       if (userAuths.size() > 0) {
145         byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
146         this.labelsCache.writeToZookeeper(serialized, false);
147         this.labelsCache.refreshUserAuthsCache(serialized);
148       }
149     }
150   }
151 
152   protected List<List<Cell>> getExistingLabelsWithAuths() throws IOException {
153     Scan scan = new Scan();
154     RegionScanner scanner = labelsRegion.getScanner(scan);
155     List<List<Cell>> existingLabels = new ArrayList<List<Cell>>();
156     try {
157       while (true) {
158         List<Cell> cells = new ArrayList<Cell>();
159         scanner.next(cells);
160         if (cells.isEmpty()) {
161           break;
162         }
163         existingLabels.add(cells);
164       }
165     } finally {
166       scanner.close();
167     }
168     return existingLabels;
169   }
170 
171   protected Pair<Map<String, Integer>, Map<String, List<Integer>>> extractLabelsAndAuths(
172       List<List<Cell>> labelDetails) {
173     Map<String, Integer> labels = new HashMap<String, Integer>();
174     Map<String, List<Integer>> userAuths = new HashMap<String, List<Integer>>();
175     for (List<Cell> cells : labelDetails) {
176       for (Cell cell : cells) {
177         if (Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(),
178             cell.getQualifierLength(), LABEL_QUALIFIER, 0, LABEL_QUALIFIER.length)) {
179           labels.put(
180               Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()),
181               Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
182         } else {
183           // These are user cells who has authorization for this label
184           String user = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
185               cell.getQualifierLength());
186           List<Integer> auths = userAuths.get(user);
187           if (auths == null) {
188             auths = new ArrayList<Integer>();
189             userAuths.put(user, auths);
190           }
191           auths.add(Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
192         }
193       }
194     }
195     return new Pair<Map<String, Integer>, Map<String, List<Integer>>>(labels, userAuths);
196   }
197 
198   protected void addSystemLabel(HRegion region, Map<String, Integer> labels,
199       Map<String, List<Integer>> userAuths) throws IOException {
200     if (!labels.containsKey(SYSTEM_LABEL)) {
201       Put p = new Put(Bytes.toBytes(SYSTEM_LABEL_ORDINAL));
202       p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, Bytes.toBytes(SYSTEM_LABEL));
203       region.put(p);
204       labels.put(SYSTEM_LABEL, SYSTEM_LABEL_ORDINAL);
205     }
206   }
207 
208   @Override
209   public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
210     assert labelsRegion != null;
211     OperationStatus[] finalOpStatus = new OperationStatus[labels.size()];
212     List<Mutation> puts = new ArrayList<Mutation>(labels.size());
213     int i = 0;
214     for (byte[] label : labels) {
215       String labelStr = Bytes.toString(label);
216       if (this.labelsCache.getLabelOrdinal(labelStr) > 0) {
217         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
218             new LabelAlreadyExistsException("Label '" + labelStr + "' already exists"));
219       } else {
220         Put p = new Put(Bytes.toBytes(ordinalCounter));
221         p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, label, LABELS_TABLE_TAGS);
222         if (LOG.isDebugEnabled()) {
223           LOG.debug("Adding the label " + labelStr);
224         }
225         puts.add(p);
226         ordinalCounter++;
227       }
228       i++;
229     }
230     if (mutateLabelsRegion(puts, finalOpStatus)) {
231       updateZk(true);
232     }
233     return finalOpStatus;
234   }
235 
236   @Override
237   public OperationStatus[] setAuths(byte[] user, List<byte[]> authLabels) throws IOException {
238     assert labelsRegion != null;
239     OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
240     List<Mutation> puts = new ArrayList<Mutation>(authLabels.size());
241     int i = 0;
242     for (byte[] auth : authLabels) {
243       String authStr = Bytes.toString(auth);
244       int labelOrdinal = this.labelsCache.getLabelOrdinal(authStr);
245       if (labelOrdinal == 0) {
246         // This label is not yet added. 1st this should be added to the system
247         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
248             new InvalidLabelException("Label '" + authStr + "' doesn't exists"));
249       } else {
250         Put p = new Put(Bytes.toBytes(labelOrdinal));
251         p.addImmutable(LABELS_TABLE_FAMILY, user, DUMMY_VALUE, LABELS_TABLE_TAGS);
252         puts.add(p);
253       }
254       i++;
255     }
256     if (mutateLabelsRegion(puts, finalOpStatus)) {
257       updateZk(false);
258     }
259     return finalOpStatus;
260   }
261 
262   @Override
263   public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
264     assert labelsRegion != null;
265     OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
266     List<String> currentAuths;
267     if (AccessControlLists.isGroupPrincipal(Bytes.toString(user))) {
268       String group = AccessControlLists.getGroupName(Bytes.toString(user));
269       currentAuths = this.getGroupAuths(new String[]{group}, true);
270     }
271     else {
272       currentAuths = this.getUserAuths(user, true);
273     }
274     List<Mutation> deletes = new ArrayList<Mutation>(authLabels.size());
275     int i = 0;
276     for (byte[] authLabel : authLabels) {
277       String authLabelStr = Bytes.toString(authLabel);
278       if (currentAuths.contains(authLabelStr)) {
279         int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabelStr);
280         assert labelOrdinal > 0;
281         Delete d = new Delete(Bytes.toBytes(labelOrdinal));
282         d.deleteColumns(LABELS_TABLE_FAMILY, user);
283         deletes.add(d);
284       } else {
285         // This label is not set for the user.
286         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
287             new InvalidLabelException("Label '" + authLabelStr + "' is not set for the user "
288                 + Bytes.toString(user)));
289       }
290       i++;
291     }
292     if (mutateLabelsRegion(deletes, finalOpStatus)) {
293       updateZk(false);
294     }
295     return finalOpStatus;
296   }
297 
298   /**
299    * Adds the mutations to labels region and set the results to the finalOpStatus. finalOpStatus
300    * might have some entries in it where the OpStatus is FAILURE. We will leave those and set in
301    * others in the order.
302    * @param mutations
303    * @param finalOpStatus
304    * @return whether we need a ZK update or not.
305    */
306   private boolean mutateLabelsRegion(List<Mutation> mutations, OperationStatus[] finalOpStatus)
307       throws IOException {
308     OperationStatus[] opStatus = this.labelsRegion.batchMutate(mutations
309         .toArray(new Mutation[mutations.size()]));
310     int i = 0;
311     boolean updateZk = false;
312     for (OperationStatus status : opStatus) {
313       // Update the zk when atleast one of the mutation was added successfully.
314       updateZk = updateZk || (status.getOperationStatusCode() == OperationStatusCode.SUCCESS);
315       for (; i < finalOpStatus.length; i++) {
316         if (finalOpStatus[i] == null) {
317           finalOpStatus[i] = status;
318           break;
319         }
320       }
321     }
322     return updateZk;
323   }
324 
325   @Override
326   @Deprecated
327   public List<String> getAuths(byte[] user, boolean systemCall)
328       throws IOException {
329     return getUserAuths(user, systemCall);
330   }
331 
332   @Override
333   public List<String> getUserAuths(byte[] user, boolean systemCall)
334       throws IOException {
335     assert (labelsRegion != null || systemCall);
336     if (systemCall || labelsRegion == null) {
337       return this.labelsCache.getUserAuths(Bytes.toString(user));
338     }
339     Scan s = new Scan();
340     if (user != null && user.length > 0) {
341       s.addColumn(LABELS_TABLE_FAMILY, user);
342     }
343     Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
344         new Authorizations(SYSTEM_LABEL));
345     s.setFilter(filter);
346     ArrayList<String> auths = new ArrayList<String>();
347     RegionScanner scanner = this.labelsRegion.getScanner(s);
348     List<Cell> results = new ArrayList<Cell>(1);
349     while (true) {
350       scanner.next(results);
351       if (results.isEmpty()) break;
352       Cell cell = results.get(0);
353       int ordinal = Bytes.toInt(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
354       String label = this.labelsCache.getLabel(ordinal);
355       if (label != null) {
356         auths.add(label);
357       }
358       results.clear();
359     }
360     return auths;
361   }
362 
363   @Override
364   public List<String> getGroupAuths(String[] groups, boolean systemCall)
365       throws IOException {
366     assert (labelsRegion != null || systemCall);
367     if (systemCall || labelsRegion == null) {
368       return this.labelsCache.getGroupAuths(groups);
369     }
370     Scan s = new Scan();
371     if (groups != null && groups.length > 0) {
372       for (String group : groups) {
373         s.addColumn(LABELS_TABLE_FAMILY, Bytes.toBytes(AccessControlLists.toGroupEntry(group)));
374       }
375     }
376     Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
377         new Authorizations(SYSTEM_LABEL));
378     s.setFilter(filter);
379     Set<String> auths = new HashSet<String>();
380     RegionScanner scanner = this.labelsRegion.getScanner(s);
381     try {
382       List<Cell> results = new ArrayList<Cell>(1);
383       while (true) {
384         scanner.next(results);
385         if (results.isEmpty()) break;
386         Cell cell = results.get(0);
387         int ordinal = Bytes.toInt(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
388         String label = this.labelsCache.getLabel(ordinal);
389         if (label != null) {
390           auths.add(label);
391         }
392         results.clear();
393       }
394     } finally {
395       scanner.close();
396     }
397     return new ArrayList<String>(auths);
398   }
399 
400   @Override
401   public List<String> listLabels(String regex) throws IOException {
402     assert (labelsRegion != null);
403     Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
404         extractLabelsAndAuths(getExistingLabelsWithAuths());
405     Map<String, Integer> labels = labelsAndUserAuths.getFirst();
406     labels.remove(SYSTEM_LABEL);
407     if (regex != null) {
408       Pattern pattern = Pattern.compile(regex);
409       ArrayList<String> matchedLabels = new ArrayList<String>();
410       for (String label : labels.keySet()) {
411         if (pattern.matcher(label).matches()) {
412           matchedLabels.add(label);
413         }
414       }
415       return matchedLabels;
416     }
417     return new ArrayList<String>(labels.keySet());
418   }
419 
420   @Override
421   public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat,
422       boolean checkAuths) throws IOException {
423     Set<Integer> auths = new HashSet<Integer>();
424     if (checkAuths) {
425       User user = VisibilityUtils.getActiveUser();
426       auths.addAll(this.labelsCache.getUserAuthsAsOrdinals(user.getShortName()));
427       auths.addAll(this.labelsCache.getGroupAuthsAsOrdinals(user.getGroupNames()));
428     }
429     return VisibilityUtils.createVisibilityExpTags(visExpression, withSerializationFormat,
430         checkAuths, auths, labelsCache);
431   }
432 
433   protected void updateZk(boolean labelAddition) throws IOException {
434     // We will add to zookeeper here.
435     // TODO we should add the delta only to zk. Else this will be a very heavy op and when there are
436     // so many labels and auth in the system, we will end up adding lots of data to zk. Most
437     // possibly we will exceed zk node data limit!
438     Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
439         extractLabelsAndAuths(getExistingLabelsWithAuths());
440     Map<String, Integer> existingLabels = labelsAndUserAuths.getFirst();
441     Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
442     if (labelAddition) {
443       byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(existingLabels);
444       this.labelsCache.writeToZookeeper(serialized, true);
445     } else {
446       byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
447       this.labelsCache.writeToZookeeper(serialized, false);
448     }
449   }
450 
451   @Override
452   public VisibilityExpEvaluator getVisibilityExpEvaluator(Authorizations authorizations)
453       throws IOException {
454     // If a super user issues a get/scan, he should be able to scan the cells
455     // irrespective of the Visibility labels
456     if (isReadFromSystemAuthUser()) {
457       return new VisibilityExpEvaluator() {
458         @Override
459         public boolean evaluate(Cell cell) throws IOException {
460           return true;
461         }
462       };
463     }
464     List<String> authLabels = null;
465     for (ScanLabelGenerator scanLabelGenerator : scanLabelGenerators) {
466       try {
467         // null authorizations to be handled inside SLG impl.
468         authLabels = scanLabelGenerator.getLabels(VisibilityUtils.getActiveUser(), authorizations);
469         authLabels = (authLabels == null) ? new ArrayList<String>() : authLabels;
470         authorizations = new Authorizations(authLabels);
471       } catch (Throwable t) {
472         LOG.error(t);
473         throw new IOException(t);
474       }
475     }
476     int labelsCount = this.labelsCache.getLabelsCount();
477     final BitSet bs = new BitSet(labelsCount + 1); // ordinal is index 1 based
478     if (authLabels != null) {
479       for (String authLabel : authLabels) {
480         int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabel);
481         if (labelOrdinal != 0) {
482           bs.set(labelOrdinal);
483         }
484       }
485     }
486 
487     return new VisibilityExpEvaluator() {
488       @Override
489       public boolean evaluate(Cell cell) throws IOException {
490         boolean visibilityTagPresent = false;
491         // Save an object allocation where we can
492         if (cell.getTagsLengthUnsigned() > 0) {
493           Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
494               cell.getTagsLengthUnsigned());
495           while (tagsItr.hasNext()) {
496             boolean includeKV = true;
497             Tag tag = tagsItr.next();
498             if (tag.getType() == VISIBILITY_TAG_TYPE) {
499               visibilityTagPresent = true;
500               int offset = tag.getTagOffset();
501               int endOffset = offset + tag.getTagLength();
502               while (offset < endOffset) {
503                 Pair<Integer, Integer> result = StreamUtils
504                     .readRawVarint32(tag.getBuffer(), offset);
505                 int currLabelOrdinal = result.getFirst();
506                 if (currLabelOrdinal < 0) {
507                   // check for the absence of this label in the Scan Auth labels
508                   // ie. to check BitSet corresponding bit is 0
509                   int temp = -currLabelOrdinal;
510                   if (bs.get(temp)) {
511                     includeKV = false;
512                     break;
513                   }
514                 } else {
515                   if (!bs.get(currLabelOrdinal)) {
516                     includeKV = false;
517                     break;
518                   }
519                 }
520                 offset += result.getSecond();
521               }
522               if (includeKV) {
523                 // We got one visibility expression getting evaluated to true. Good to include this
524                 // KV in the result then.
525                 return true;
526               }
527             }
528           }
529         }
530         return !(visibilityTagPresent);
531       }
532     };
533   }
534 
535   protected boolean isReadFromSystemAuthUser() throws IOException {
536     User user = VisibilityUtils.getActiveUser();
537     return havingSystemAuth(user);
538   }
539 
540   @Override
541   @Deprecated
542   public boolean havingSystemAuth(byte[] user) throws IOException {
543     // Implementation for backward compatibility
544     if (this.superUsers.contains(Bytes.toString(user))) {
545       return true;
546     }
547     List<String> auths = this.getUserAuths(user, true);
548     if (LOG.isTraceEnabled()) {
549       LOG.trace("The auths for user " + Bytes.toString(user) + " are " + auths);
550     }
551     return auths.contains(SYSTEM_LABEL);
552   }
553 
554   @Override
555   public boolean havingSystemAuth(User user) throws IOException {
556     // A super user has 'system' auth.
557     if (isSystemOrSuperUser(user)) {
558       return true;
559     }
560     // A user can also be explicitly granted 'system' auth.
561     List<String> auths = this.getUserAuths(Bytes.toBytes(user.getShortName()), true);
562     if (LOG.isTraceEnabled()) {
563       LOG.trace("The auths for user " + user.getShortName() + " are " + auths);
564     }
565     if (auths.contains(SYSTEM_LABEL)) {
566       return true;
567     }
568     auths = this.getGroupAuths(user.getGroupNames(), true);
569     if (LOG.isTraceEnabled()) {
570       LOG.trace("The auths for groups of user " + user.getShortName() + " are " + auths);
571     }
572     return auths.contains(SYSTEM_LABEL);
573   }
574 
575   private boolean isSystemOrSuperUser(User user) throws IOException {
576     if (this.superUsers.contains(user.getShortName())) {
577       return true;
578     }
579     String[] groups = user.getGroupNames();
580     if (groups != null && groups.length > 0) {
581       for (String group : groups) {
582         if (this.superGroups.contains(group)) {
583           return true;
584         }
585       }
586     }
587     return false;
588   }
589 
590   @Override
591   public boolean matchVisibility(List<Tag> putVisTags, Byte putTagsFormat, List<Tag> deleteVisTags,
592       Byte deleteTagsFormat) throws IOException {
593     if ((deleteTagsFormat != null && deleteTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)
594         && (putTagsFormat == null || putTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)) {
595       if (putVisTags.size() == 0) {
596         // Early out if there are no tags in the cell
597         return false;
598       }
599       if (putTagsFormat == null) {
600         return matchUnSortedVisibilityTags(putVisTags, deleteVisTags);
601       } else {
602         return matchOrdinalSortedVisibilityTags(putVisTags, deleteVisTags);
603       }
604     }
605     throw new IOException("Unexpected tag format passed for comparison, deleteTagsFormat : "
606         + deleteTagsFormat + ", putTagsFormat : " + putTagsFormat);
607   }
608 
609   /**
610    * @param putVisTags Visibility tags in Put Mutation
611    * @param deleteVisTags Visibility tags in Delete Mutation
612    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
613    * This is used when, at least one set of tags are not sorted based on the label ordinal.
614    */
615   private static boolean matchUnSortedVisibilityTags(List<Tag> putVisTags,
616       List<Tag> deleteVisTags) throws IOException {
617     return compareTagsOrdinals(sortTagsBasedOnOrdinal(putVisTags),
618         sortTagsBasedOnOrdinal(deleteVisTags));
619   }
620 
621   /**
622    * @param putVisTags Visibility tags in Put Mutation
623    * @param deleteVisTags Visibility tags in Delete Mutation
624    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
625    * This is used when both the set of tags are sorted based on the label ordinal.
626    */
627   private static boolean matchOrdinalSortedVisibilityTags(List<Tag> putVisTags,
628       List<Tag> deleteVisTags) {
629     boolean matchFound = false;
630     // If the size does not match. Definitely we are not comparing the equal tags.
631     if ((deleteVisTags.size()) == putVisTags.size()) {
632       for (Tag tag : deleteVisTags) {
633         matchFound = false;
634         for (Tag givenTag : putVisTags) {
635           if (Bytes.equals(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength(),
636               givenTag.getBuffer(), givenTag.getTagOffset(), givenTag.getTagLength())) {
637             matchFound = true;
638             break;
639           }
640         }
641         if (!matchFound) break;
642       }
643     }
644     return matchFound;
645   }
646 
647   private static List<List<Integer>> sortTagsBasedOnOrdinal(List<Tag> tags) throws IOException {
648     List<List<Integer>> fullTagsList = new ArrayList<List<Integer>>();
649     for (Tag tag : tags) {
650       if (tag.getType() == VISIBILITY_TAG_TYPE) {
651         getSortedTagOrdinals(fullTagsList, tag);
652       }
653     }
654     return fullTagsList;
655   }
656 
657   private static void getSortedTagOrdinals(List<List<Integer>> fullTagsList, Tag tag)
658       throws IOException {
659     List<Integer> tagsOrdinalInSortedOrder = new ArrayList<Integer>();
660     int offset = tag.getTagOffset();
661     int endOffset = offset + tag.getTagLength();
662     while (offset < endOffset) {
663       Pair<Integer, Integer> result = StreamUtils.readRawVarint32(tag.getBuffer(), offset);
664       tagsOrdinalInSortedOrder.add(result.getFirst());
665       offset += result.getSecond();
666     }
667     Collections.sort(tagsOrdinalInSortedOrder);
668     fullTagsList.add(tagsOrdinalInSortedOrder);
669   }
670 
671   /*
672    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
673    */
674   private static boolean compareTagsOrdinals(List<List<Integer>> putVisTags,
675       List<List<Integer>> deleteVisTags) {
676     boolean matchFound = false;
677     if (deleteVisTags.size() == putVisTags.size()) {
678       for (List<Integer> deleteTagOrdinals : deleteVisTags) {
679         matchFound = false;
680         for (List<Integer> tagOrdinals : putVisTags) {
681           if (deleteTagOrdinals.equals(tagOrdinals)) {
682             matchFound = true;
683             break;
684           }
685         }
686         if (!matchFound) break;
687       }
688     }
689     return matchFound;
690   }
691 
692   @Override
693   public byte[] encodeVisibilityForReplication(final List<Tag> tags, final Byte serializationFormat)
694       throws IOException {
695     if (tags.size() > 0
696         && (serializationFormat == null ||
697         serializationFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)) {
698       return createModifiedVisExpression(tags);
699     }
700     return null;
701   }
702 
703   /**
704    * @param tags
705    *          - all the visibility tags associated with the current Cell
706    * @return - the modified visibility expression as byte[]
707    */
708   private byte[] createModifiedVisExpression(final List<Tag> tags)
709       throws IOException {
710     StringBuilder visibilityString = new StringBuilder();
711     for (Tag tag : tags) {
712       if (tag.getType() == TagType.VISIBILITY_TAG_TYPE) {
713         if (visibilityString.length() != 0) {
714           visibilityString.append(VisibilityConstants.CLOSED_PARAN).append(
715               VisibilityConstants.OR_OPERATOR);
716         }
717         int offset = tag.getTagOffset();
718         int endOffset = offset + tag.getTagLength();
719         boolean expressionStart = true;
720         while (offset < endOffset) {
721           Pair<Integer, Integer> result = StreamUtils.readRawVarint32(tag.getBuffer(), offset);
722           int currLabelOrdinal = result.getFirst();
723           if (currLabelOrdinal < 0) {
724             int temp = -currLabelOrdinal;
725             String label = this.labelsCache.getLabel(temp);
726             if (expressionStart) {
727               // Quote every label in case of unicode characters if present
728               visibilityString.append(VisibilityConstants.OPEN_PARAN)
729                   .append(VisibilityConstants.NOT_OPERATOR).append(CellVisibility.quote(label));
730             } else {
731               visibilityString.append(VisibilityConstants.AND_OPERATOR)
732                   .append(VisibilityConstants.NOT_OPERATOR).append(CellVisibility.quote(label));
733             }
734           } else {
735             String label = this.labelsCache.getLabel(currLabelOrdinal);
736             if (expressionStart) {
737               visibilityString.append(VisibilityConstants.OPEN_PARAN).append(
738                   CellVisibility.quote(label));
739             } else {
740               visibilityString.append(VisibilityConstants.AND_OPERATOR).append(
741                   CellVisibility.quote(label));
742             }
743           }
744           expressionStart = false;
745           offset += result.getSecond();
746         }
747       }
748     }
749     if (visibilityString.length() != 0) {
750       visibilityString.append(VisibilityConstants.CLOSED_PARAN);
751       // Return the string formed as byte[]
752       return Bytes.toBytes(visibilityString.toString());
753     }
754     return null;
755   }
756 }