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}