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