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.Stable
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  @InterfaceStability.Unstable
104  public String toString() {
105    // This currently just delegates to the stable string representation, but it
106    // is permissible for the output of this method to change across versions.
107    return toStringStable();
108  }
109
110  /**
111   * Returns a string representation guaranteed to be stable across versions to
112   * satisfy backward compatibility requirements, such as for shell command
113   * output or serialization.  The format of this string representation matches
114   * what is expected by the {@link #parseAclSpec(String, boolean)} and
115   * {@link #parseAclEntry(String, boolean)} methods.
116   *
117   * @return stable, backward compatible string representation
118   */
119  public String toStringStable() {
120    StringBuilder sb = new StringBuilder();
121    if (scope == AclEntryScope.DEFAULT) {
122      sb.append("default:");
123    }
124    if (type != null) {
125      sb.append(StringUtils.toLowerCase(type.toStringStable()));
126    }
127    sb.append(':');
128    if (name != null) {
129      sb.append(name);
130    }
131    sb.append(':');
132    if (permission != null) {
133      sb.append(permission.SYMBOL);
134    }
135    return sb.toString();
136  }
137
138  /**
139   * Builder for creating new AclEntry instances.
140   */
141  public static class Builder {
142    private AclEntryType type;
143    private String name;
144    private FsAction permission;
145    private AclEntryScope scope = AclEntryScope.ACCESS;
146
147    /**
148     * Sets the ACL entry type.
149     *
150     * @param type AclEntryType ACL entry type
151     * @return Builder this builder, for call chaining
152     */
153    public Builder setType(AclEntryType type) {
154      this.type = type;
155      return this;
156    }
157
158    /**
159     * Sets the optional ACL entry name.
160     *
161     * @param name String optional ACL entry name
162     * @return Builder this builder, for call chaining
163     */
164    public Builder setName(String name) {
165      if (name != null && !name.isEmpty()) {
166        this.name = name;
167      }
168      return this;
169    }
170
171    /**
172     * Sets the set of permissions in the ACL entry.
173     *
174     * @param permission FsAction set of permissions in the ACL entry
175     * @return Builder this builder, for call chaining
176     */
177    public Builder setPermission(FsAction permission) {
178      this.permission = permission;
179      return this;
180    }
181
182    /**
183     * Sets the scope of the ACL entry.  If this method is not called, then the
184     * builder assumes {@link AclEntryScope#ACCESS}.
185     *
186     * @param scope AclEntryScope scope of the ACL entry
187     * @return Builder this builder, for call chaining
188     */
189    public Builder setScope(AclEntryScope scope) {
190      this.scope = scope;
191      return this;
192    }
193
194    /**
195     * Builds a new AclEntry populated with the set properties.
196     *
197     * @return AclEntry new AclEntry
198     */
199    public AclEntry build() {
200      return new AclEntry(type, name, permission, scope);
201    }
202  }
203
204  /**
205   * Private constructor.
206   *
207   * @param type AclEntryType ACL entry type
208   * @param name String optional ACL entry name
209   * @param permission FsAction set of permissions in the ACL entry
210   * @param scope AclEntryScope scope of the ACL entry
211   */
212  private AclEntry(AclEntryType type, String name, FsAction permission, AclEntryScope scope) {
213    this.type = type;
214    this.name = name;
215    this.permission = permission;
216    this.scope = scope;
217  }
218
219  /**
220   * Parses a string representation of an ACL spec into a list of AclEntry
221   * objects. Example: "user::rwx,user:foo:rw-,group::r--,other::---"
222   * The expected format of ACL entries in the string parameter is the same
223   * format produced by the {@link #toStringStable()} method.
224   * 
225   * @param aclSpec
226   *          String representation of an ACL spec.
227   * @param includePermission
228   *          for setAcl operations this will be true. i.e. AclSpec should
229   *          include permissions.<br>
230   *          But for removeAcl operation it will be false. i.e. AclSpec should
231   *          not contain permissions.<br>
232   *          Example: "user:foo,group:bar"
233   * @return Returns list of {@link AclEntry} parsed
234   */
235  public static List<AclEntry> parseAclSpec(String aclSpec,
236      boolean includePermission) {
237    List<AclEntry> aclEntries = new ArrayList<AclEntry>();
238    Collection<String> aclStrings = StringUtils.getStringCollection(aclSpec,
239        ",");
240    for (String aclStr : aclStrings) {
241      AclEntry aclEntry = parseAclEntry(aclStr, includePermission);
242      aclEntries.add(aclEntry);
243    }
244    return aclEntries;
245  }
246
247  /**
248   * Parses a string representation of an ACL into a AclEntry object.<br>
249   * The expected format of ACL entries in the string parameter is the same
250   * format produced by the {@link #toStringStable()} method.
251   * 
252   * @param aclStr
253   *          String representation of an ACL.<br>
254   *          Example: "user:foo:rw-"
255   * @param includePermission
256   *          for setAcl operations this will be true. i.e. Acl should include
257   *          permissions.<br>
258   *          But for removeAcl operation it will be false. i.e. Acl should not
259   *          contain permissions.<br>
260   *          Example: "user:foo,group:bar,mask::"
261   * @return Returns an {@link AclEntry} object
262   */
263  public static AclEntry parseAclEntry(String aclStr,
264      boolean includePermission) {
265    AclEntry.Builder builder = new AclEntry.Builder();
266    // Here "::" represent one empty string.
267    // StringUtils.getStringCollection() will ignore this.
268    String[] split = aclStr.split(":");
269
270    if (split.length == 0) {
271      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
272    }
273    int index = 0;
274    if ("default".equals(split[0])) {
275      // default entry
276      index++;
277      builder.setScope(AclEntryScope.DEFAULT);
278    }
279
280    if (split.length <= index) {
281      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
282    }
283
284    AclEntryType aclType = null;
285    try {
286      aclType = Enum.valueOf(
287          AclEntryType.class, StringUtils.toUpperCase(split[index]));
288      builder.setType(aclType);
289      index++;
290    } catch (IllegalArgumentException iae) {
291      throw new HadoopIllegalArgumentException(
292          "Invalid type of acl in <aclSpec> :" + aclStr);
293    }
294
295    if (split.length > index) {
296      String name = split[index];
297      if (!name.isEmpty()) {
298        builder.setName(name);
299      }
300      index++;
301    }
302
303    if (includePermission) {
304      if (split.length <= index) {
305        throw new HadoopIllegalArgumentException("Invalid <aclSpec> : "
306            + aclStr);
307      }
308      String permission = split[index];
309      FsAction fsAction = FsAction.getFsAction(permission);
310      if (null == fsAction) {
311        throw new HadoopIllegalArgumentException(
312            "Invalid permission in <aclSpec> : " + aclStr);
313      }
314      builder.setPermission(fsAction);
315      index++;
316    }
317
318    if (split.length > index) {
319      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
320    }
321    AclEntry aclEntry = builder.build();
322    return aclEntry;
323  }
324
325  /**
326   * Convert a List of AclEntries into a string - the reverse of parseAclSpec.
327   * @param aclSpec List of AclEntries to convert
328   * @return String representation of aclSpec
329   */
330  public static String aclSpecToString(List<AclEntry> aclSpec) {
331    StringBuilder buf = new StringBuilder();
332    for ( AclEntry e : aclSpec ) {
333      buf.append(e.toString());
334      buf.append(",");
335    }
336    return buf.substring(0, buf.length()-1);  // remove last ,
337  }
338}