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.DataInput;
021import java.io.DataOutput;
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.EnumSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Objects;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.util.Bytes;
031import org.apache.hadoop.io.VersionedWritable;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
037
038/**
039 * Base permissions instance representing the ability to perform a given set of actions.
040 * @see TablePermission
041 */
042@InterfaceAudience.Public
043public class Permission extends VersionedWritable {
044  protected static final byte VERSION = 0;
045
046  @InterfaceAudience.Public
047  public enum Action {
048    READ('R'),
049    WRITE('W'),
050    EXEC('X'),
051    CREATE('C'),
052    ADMIN('A');
053
054    private final byte code;
055
056    Action(char code) {
057      this.code = (byte) code;
058    }
059
060    public byte code() {
061      return code;
062    }
063  }
064
065  @InterfaceAudience.Private
066  protected enum Scope {
067    GLOBAL('G'),
068    NAMESPACE('N'),
069    TABLE('T'),
070    EMPTY('E');
071
072    private final byte code;
073
074    Scope(char code) {
075      this.code = (byte) code;
076    }
077
078    public byte code() {
079      return code;
080    }
081  }
082
083  private static final Logger LOG = LoggerFactory.getLogger(Permission.class);
084
085  protected static final Map<Byte, Action> ACTION_BY_CODE;
086  protected static final Map<Byte, Scope> SCOPE_BY_CODE;
087
088  protected EnumSet<Action> actions = EnumSet.noneOf(Action.class);
089  protected Scope scope = Scope.EMPTY;
090
091  static {
092    ACTION_BY_CODE = ImmutableMap.of(Action.READ.code, Action.READ, Action.WRITE.code, Action.WRITE,
093      Action.EXEC.code, Action.EXEC, Action.CREATE.code, Action.CREATE, Action.ADMIN.code,
094      Action.ADMIN);
095
096    SCOPE_BY_CODE = ImmutableMap.of(Scope.GLOBAL.code, Scope.GLOBAL, Scope.NAMESPACE.code,
097      Scope.NAMESPACE, Scope.TABLE.code, Scope.TABLE, Scope.EMPTY.code, Scope.EMPTY);
098  }
099
100  /** Empty constructor for Writable implementation. <b>Do not use.</b> */
101  public Permission() {
102    super();
103  }
104
105  public Permission(Action... assigned) {
106    if (assigned != null && assigned.length > 0) {
107      actions.addAll(Arrays.asList(assigned));
108    }
109  }
110
111  public Permission(byte[] actionCodes) {
112    if (actionCodes != null) {
113      for (byte code : actionCodes) {
114        Action action = ACTION_BY_CODE.get(code);
115        if (action == null) {
116          LOG.error(
117            "Ignoring unknown action code '" + Bytes.toStringBinary(new byte[] { code }) + "'");
118          continue;
119        }
120        actions.add(action);
121      }
122    }
123  }
124
125  public Action[] getActions() {
126    return actions.toArray(new Action[actions.size()]);
127  }
128
129  /**
130   * check if given action is granted
131   * @param action action to be checked
132   * @return true if granted, false otherwise
133   */
134  public boolean implies(Action action) {
135    return actions.contains(action);
136  }
137
138  public void setActions(Action[] assigned) {
139    if (assigned != null && assigned.length > 0) {
140      // setActions should cover the previous actions,
141      // so we call clear here.
142      actions.clear();
143      actions.addAll(Arrays.asList(assigned));
144    }
145  }
146
147  /**
148   * Check if two permission equals regardless of actions. It is useful when merging a new
149   * permission with an existed permission which needs to check two permissions's fields.
150   * @param obj instance
151   * @return true if equals, false otherwise
152   */
153  public boolean equalsExceptActions(Object obj) {
154    return obj instanceof Permission;
155  }
156
157  @Override
158  public boolean equals(Object obj) {
159    if (!(obj instanceof Permission)) {
160      return false;
161    }
162
163    Permission other = (Permission) obj;
164    if (actions.isEmpty() && other.actions.isEmpty()) {
165      return true;
166    } else if (!actions.isEmpty() && !other.actions.isEmpty()) {
167      if (actions.size() != other.actions.size()) {
168        return false;
169      }
170      return actions.containsAll(other.actions);
171    }
172    return false;
173  }
174
175  @Override
176  public int hashCode() {
177    final int prime = 37;
178    int result = 23;
179    for (Action a : actions) {
180      result = prime * result + a.code();
181    }
182    result = prime * result + scope.code();
183    return result;
184  }
185
186  @Override
187  public String toString() {
188    return "[Permission: " + rawExpression() + "]";
189  }
190
191  protected String rawExpression() {
192    StringBuilder raw = new StringBuilder("actions=");
193    if (actions != null) {
194      int i = 0;
195      for (Action action : actions) {
196        if (i > 0) {
197          raw.append(",");
198        }
199        raw.append(action != null ? action.toString() : "NULL");
200        i++;
201      }
202    }
203    return raw.toString();
204  }
205
206  /** Returns the object version number */
207  @Override
208  public byte getVersion() {
209    return VERSION;
210  }
211
212  @Override
213  public void readFields(DataInput in) throws IOException {
214    super.readFields(in);
215    int length = (int) in.readByte();
216    actions = EnumSet.noneOf(Action.class);
217    if (length > 0) {
218      for (int i = 0; i < length; i++) {
219        byte b = in.readByte();
220        Action action = ACTION_BY_CODE.get(b);
221        if (action == null) {
222          throw new IOException(
223            "Unknown action code '" + Bytes.toStringBinary(new byte[] { b }) + "' in input");
224        }
225        actions.add(action);
226      }
227    }
228    scope = SCOPE_BY_CODE.get(in.readByte());
229  }
230
231  @Override
232  public void write(DataOutput out) throws IOException {
233    super.write(out);
234    out.writeByte(actions != null ? actions.size() : 0);
235    if (actions != null) {
236      for (Action a : actions) {
237        out.writeByte(a.code());
238      }
239    }
240    out.writeByte(scope.code());
241  }
242
243  public Scope getAccessScope() {
244    return scope;
245  }
246
247  /**
248   * Build a global permission
249   * @return global permission builder
250   */
251  public static Builder newBuilder() {
252    return new Builder();
253  }
254
255  /**
256   * Build a namespace permission
257   * @param namespace the specific namespace
258   * @return namespace permission builder
259   */
260  public static Builder newBuilder(String namespace) {
261    return new Builder(namespace);
262  }
263
264  /**
265   * Build a table permission
266   * @param tableName the specific table name
267   * @return table permission builder
268   */
269  public static Builder newBuilder(TableName tableName) {
270    return new Builder(tableName);
271  }
272
273  public static final class Builder {
274    private String namespace;
275    private TableName tableName;
276    private byte[] family;
277    private byte[] qualifier;
278    private List<Action> actions = new ArrayList<>();
279
280    private Builder() {
281    }
282
283    private Builder(String namespace) {
284      this.namespace = namespace;
285    }
286
287    private Builder(TableName tableName) {
288      this.tableName = tableName;
289    }
290
291    public Builder withFamily(byte[] family) {
292      Objects.requireNonNull(tableName, "The tableName can't be NULL");
293      this.family = family;
294      return this;
295    }
296
297    public Builder withQualifier(byte[] qualifier) {
298      Objects.requireNonNull(tableName, "The tableName can't be NULL");
299      this.qualifier = qualifier;
300      return this;
301    }
302
303    public Builder withActions(Action... actions) {
304      for (Action action : actions) {
305        if (action != null) {
306          this.actions.add(action);
307        }
308      }
309      return this;
310    }
311
312    public Builder withActionCodes(byte[] actionCodes) {
313      if (actionCodes != null) {
314        for (byte code : actionCodes) {
315          Action action = ACTION_BY_CODE.get(code);
316          if (action == null) {
317            LOG.error("Ignoring unknown action code '{}'",
318              Bytes.toStringBinary(new byte[] { code }));
319            continue;
320          }
321          this.actions.add(action);
322        }
323      }
324      return this;
325    }
326
327    public Permission build() {
328      Action[] actionArray = actions.toArray(new Action[actions.size()]);
329      if (namespace != null) {
330        return new NamespacePermission(namespace, actionArray);
331      } else if (tableName != null) {
332        return new TablePermission(tableName, family, qualifier, actionArray);
333      } else {
334        return new GlobalPermission(actionArray);
335      }
336    }
337  }
338
339}