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