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}