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}