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.fs.permission;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.List;
023
024import com.google.common.base.Objects;
025
026import org.apache.hadoop.HadoopIllegalArgumentException;
027import org.apache.hadoop.classification.InterfaceAudience;
028import org.apache.hadoop.classification.InterfaceStability;
029import org.apache.hadoop.util.StringUtils;
030
031/**
032 * Defines a single entry in an ACL.  An ACL entry has a type (user, group,
033 * mask, or other), an optional name (referring to a specific user or group), a
034 * set of permissions (any combination of read, write and execute), and a scope
035 * (access or default).  AclEntry instances are immutable.  Use a {@link Builder}
036 * to create a new instance.
037 */
038@InterfaceAudience.Public
039@InterfaceStability.Evolving
040public class AclEntry {
041  private final AclEntryType type;
042  private final String name;
043  private final FsAction permission;
044  private final AclEntryScope scope;
045
046  /**
047   * Returns the ACL entry type.
048   *
049   * @return AclEntryType ACL entry type
050   */
051  public AclEntryType getType() {
052    return type;
053  }
054
055  /**
056   * Returns the optional ACL entry name.
057   *
058   * @return String ACL entry name, or null if undefined
059   */
060  public String getName() {
061    return name;
062  }
063
064  /**
065   * Returns the set of permissions in the ACL entry.
066   *
067   * @return FsAction set of permissions in the ACL entry
068   */
069  public FsAction getPermission() {
070    return permission;
071  }
072
073  /**
074   * Returns the scope of the ACL entry.
075   *
076   * @return AclEntryScope scope of the ACL entry
077   */
078  public AclEntryScope getScope() {
079    return scope;
080  }
081
082  @Override
083  public boolean equals(Object o) {
084    if (o == null) {
085      return false;
086    }
087    if (getClass() != o.getClass()) {
088      return false;
089    }
090    AclEntry other = (AclEntry)o;
091    return Objects.equal(type, other.type) &&
092      Objects.equal(name, other.name) &&
093      Objects.equal(permission, other.permission) &&
094      Objects.equal(scope, other.scope);
095  }
096
097  @Override
098  public int hashCode() {
099    return Objects.hashCode(type, name, permission, scope);
100  }
101
102  @Override
103  public String toString() {
104    StringBuilder sb = new StringBuilder();
105    if (scope == AclEntryScope.DEFAULT) {
106      sb.append("default:");
107    }
108    if (type != null) {
109      sb.append(type.toString().toLowerCase());
110    }
111    sb.append(':');
112    if (name != null) {
113      sb.append(name);
114    }
115    sb.append(':');
116    if (permission != null) {
117      sb.append(permission.SYMBOL);
118    }
119    return sb.toString();
120  }
121
122  /**
123   * Builder for creating new AclEntry instances.
124   */
125  public static class Builder {
126    private AclEntryType type;
127    private String name;
128    private FsAction permission;
129    private AclEntryScope scope = AclEntryScope.ACCESS;
130
131    /**
132     * Sets the ACL entry type.
133     *
134     * @param type AclEntryType ACL entry type
135     * @return Builder this builder, for call chaining
136     */
137    public Builder setType(AclEntryType type) {
138      this.type = type;
139      return this;
140    }
141
142    /**
143     * Sets the optional ACL entry name.
144     *
145     * @param name String optional ACL entry name
146     * @return Builder this builder, for call chaining
147     */
148    public Builder setName(String name) {
149      this.name = name;
150      return this;
151    }
152
153    /**
154     * Sets the set of permissions in the ACL entry.
155     *
156     * @param permission FsAction set of permissions in the ACL entry
157     * @return Builder this builder, for call chaining
158     */
159    public Builder setPermission(FsAction permission) {
160      this.permission = permission;
161      return this;
162    }
163
164    /**
165     * Sets the scope of the ACL entry.  If this method is not called, then the
166     * builder assumes {@link AclEntryScope#ACCESS}.
167     *
168     * @param scope AclEntryScope scope of the ACL entry
169     * @return Builder this builder, for call chaining
170     */
171    public Builder setScope(AclEntryScope scope) {
172      this.scope = scope;
173      return this;
174    }
175
176    /**
177     * Builds a new AclEntry populated with the set properties.
178     *
179     * @return AclEntry new AclEntry
180     */
181    public AclEntry build() {
182      return new AclEntry(type, name, permission, scope);
183    }
184  }
185
186  /**
187   * Private constructor.
188   *
189   * @param type AclEntryType ACL entry type
190   * @param name String optional ACL entry name
191   * @param permission FsAction set of permissions in the ACL entry
192   * @param scope AclEntryScope scope of the ACL entry
193   */
194  private AclEntry(AclEntryType type, String name, FsAction permission, AclEntryScope scope) {
195    this.type = type;
196    this.name = name;
197    this.permission = permission;
198    this.scope = scope;
199  }
200
201  /**
202   * Parses a string representation of an ACL spec into a list of AclEntry
203   * objects. Example: "user::rwx,user:foo:rw-,group::r--,other::---"
204   * 
205   * @param aclSpec
206   *          String representation of an ACL spec.
207   * @param includePermission
208   *          for setAcl operations this will be true. i.e. AclSpec should
209   *          include permissions.<br>
210   *          But for removeAcl operation it will be false. i.e. AclSpec should
211   *          not contain permissions.<br>
212   *          Example: "user:foo,group:bar"
213   * @return Returns list of {@link AclEntry} parsed
214   */
215  public static List<AclEntry> parseAclSpec(String aclSpec,
216      boolean includePermission) {
217    List<AclEntry> aclEntries = new ArrayList<AclEntry>();
218    Collection<String> aclStrings = StringUtils.getStringCollection(aclSpec,
219        ",");
220    for (String aclStr : aclStrings) {
221      AclEntry aclEntry = parseAclEntry(aclStr, includePermission);
222      aclEntries.add(aclEntry);
223    }
224    return aclEntries;
225  }
226
227  /**
228   * Parses a string representation of an ACL into a AclEntry object.<br>
229   * 
230   * @param aclStr
231   *          String representation of an ACL.<br>
232   *          Example: "user:foo:rw-"
233   * @param includePermission
234   *          for setAcl operations this will be true. i.e. Acl should include
235   *          permissions.<br>
236   *          But for removeAcl operation it will be false. i.e. Acl should not
237   *          contain permissions.<br>
238   *          Example: "user:foo,group:bar,mask::"
239   * @return Returns an {@link AclEntry} object
240   */
241  public static AclEntry parseAclEntry(String aclStr,
242      boolean includePermission) {
243    AclEntry.Builder builder = new AclEntry.Builder();
244    // Here "::" represent one empty string.
245    // StringUtils.getStringCollection() will ignore this.
246    String[] split = aclStr.split(":");
247
248    if (split.length == 0) {
249      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
250    }
251    int index = 0;
252    if ("default".equals(split[0])) {
253      // default entry
254      index++;
255      builder.setScope(AclEntryScope.DEFAULT);
256    }
257
258    if (split.length <= index) {
259      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
260    }
261
262    AclEntryType aclType = null;
263    try {
264      aclType = Enum.valueOf(AclEntryType.class, split[index].toUpperCase());
265      builder.setType(aclType);
266      index++;
267    } catch (IllegalArgumentException iae) {
268      throw new HadoopIllegalArgumentException(
269          "Invalid type of acl in <aclSpec> :" + aclStr);
270    }
271
272    if (split.length > index) {
273      String name = split[index];
274      if (!name.isEmpty()) {
275        builder.setName(name);
276      }
277      index++;
278    }
279
280    if (includePermission) {
281      if (split.length <= index) {
282        throw new HadoopIllegalArgumentException("Invalid <aclSpec> : "
283            + aclStr);
284      }
285      String permission = split[index];
286      FsAction fsAction = FsAction.getFsAction(permission);
287      if (null == fsAction) {
288        throw new HadoopIllegalArgumentException(
289            "Invalid permission in <aclSpec> : " + aclStr);
290      }
291      builder.setPermission(fsAction);
292      index++;
293    }
294
295    if (split.length > index) {
296      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
297    }
298    AclEntry aclEntry = builder.build();
299    return aclEntry;
300  }
301
302  /**
303   * Convert a List of AclEntries into a string - the reverse of parseAclSpec.
304   * @param aclSpec List of AclEntries to convert
305   * @return String representation of aclSpec
306   */
307  public static String aclSpecToString(List<AclEntry> aclSpec) {
308    StringBuilder buf = new StringBuilder();
309    for ( AclEntry e : aclSpec ) {
310      buf.append(e.toString());
311      buf.append(",");
312    }
313    return buf.substring(0, buf.length()-1);  // remove last ,
314  }
315}