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(StringUtils.toLowerCase(type.toString()));
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      if (name != null && !name.isEmpty()) {
150        this.name = name;
151      }
152      return this;
153    }
154
155    /**
156     * Sets the set of permissions in the ACL entry.
157     *
158     * @param permission FsAction set of permissions in the ACL entry
159     * @return Builder this builder, for call chaining
160     */
161    public Builder setPermission(FsAction permission) {
162      this.permission = permission;
163      return this;
164    }
165
166    /**
167     * Sets the scope of the ACL entry.  If this method is not called, then the
168     * builder assumes {@link AclEntryScope#ACCESS}.
169     *
170     * @param scope AclEntryScope scope of the ACL entry
171     * @return Builder this builder, for call chaining
172     */
173    public Builder setScope(AclEntryScope scope) {
174      this.scope = scope;
175      return this;
176    }
177
178    /**
179     * Builds a new AclEntry populated with the set properties.
180     *
181     * @return AclEntry new AclEntry
182     */
183    public AclEntry build() {
184      return new AclEntry(type, name, permission, scope);
185    }
186  }
187
188  /**
189   * Private constructor.
190   *
191   * @param type AclEntryType ACL entry type
192   * @param name String optional ACL entry name
193   * @param permission FsAction set of permissions in the ACL entry
194   * @param scope AclEntryScope scope of the ACL entry
195   */
196  private AclEntry(AclEntryType type, String name, FsAction permission, AclEntryScope scope) {
197    this.type = type;
198    this.name = name;
199    this.permission = permission;
200    this.scope = scope;
201  }
202
203  /**
204   * Parses a string representation of an ACL spec into a list of AclEntry
205   * objects. Example: "user::rwx,user:foo:rw-,group::r--,other::---"
206   * 
207   * @param aclSpec
208   *          String representation of an ACL spec.
209   * @param includePermission
210   *          for setAcl operations this will be true. i.e. AclSpec should
211   *          include permissions.<br>
212   *          But for removeAcl operation it will be false. i.e. AclSpec should
213   *          not contain permissions.<br>
214   *          Example: "user:foo,group:bar"
215   * @return Returns list of {@link AclEntry} parsed
216   */
217  public static List<AclEntry> parseAclSpec(String aclSpec,
218      boolean includePermission) {
219    List<AclEntry> aclEntries = new ArrayList<AclEntry>();
220    Collection<String> aclStrings = StringUtils.getStringCollection(aclSpec,
221        ",");
222    for (String aclStr : aclStrings) {
223      AclEntry aclEntry = parseAclEntry(aclStr, includePermission);
224      aclEntries.add(aclEntry);
225    }
226    return aclEntries;
227  }
228
229  /**
230   * Parses a string representation of an ACL into a AclEntry object.<br>
231   * 
232   * @param aclStr
233   *          String representation of an ACL.<br>
234   *          Example: "user:foo:rw-"
235   * @param includePermission
236   *          for setAcl operations this will be true. i.e. Acl should include
237   *          permissions.<br>
238   *          But for removeAcl operation it will be false. i.e. Acl should not
239   *          contain permissions.<br>
240   *          Example: "user:foo,group:bar,mask::"
241   * @return Returns an {@link AclEntry} object
242   */
243  public static AclEntry parseAclEntry(String aclStr,
244      boolean includePermission) {
245    AclEntry.Builder builder = new AclEntry.Builder();
246    // Here "::" represent one empty string.
247    // StringUtils.getStringCollection() will ignore this.
248    String[] split = aclStr.split(":");
249
250    if (split.length == 0) {
251      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
252    }
253    int index = 0;
254    if ("default".equals(split[0])) {
255      // default entry
256      index++;
257      builder.setScope(AclEntryScope.DEFAULT);
258    }
259
260    if (split.length <= index) {
261      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
262    }
263
264    AclEntryType aclType = null;
265    try {
266      aclType = Enum.valueOf(
267          AclEntryType.class, StringUtils.toUpperCase(split[index]));
268      builder.setType(aclType);
269      index++;
270    } catch (IllegalArgumentException iae) {
271      throw new HadoopIllegalArgumentException(
272          "Invalid type of acl in <aclSpec> :" + aclStr);
273    }
274
275    if (split.length > index) {
276      String name = split[index];
277      if (!name.isEmpty()) {
278        builder.setName(name);
279      }
280      index++;
281    }
282
283    if (includePermission) {
284      if (split.length <= index) {
285        throw new HadoopIllegalArgumentException("Invalid <aclSpec> : "
286            + aclStr);
287      }
288      String permission = split[index];
289      FsAction fsAction = FsAction.getFsAction(permission);
290      if (null == fsAction) {
291        throw new HadoopIllegalArgumentException(
292            "Invalid permission in <aclSpec> : " + aclStr);
293      }
294      builder.setPermission(fsAction);
295      index++;
296    }
297
298    if (split.length > index) {
299      throw new HadoopIllegalArgumentException("Invalid <aclSpec> : " + aclStr);
300    }
301    AclEntry aclEntry = builder.build();
302    return aclEntry;
303  }
304
305  /**
306   * Convert a List of AclEntries into a string - the reverse of parseAclSpec.
307   * @param aclSpec List of AclEntries to convert
308   * @return String representation of aclSpec
309   */
310  public static String aclSpecToString(List<AclEntry> aclSpec) {
311    StringBuilder buf = new StringBuilder();
312    for ( AclEntry e : aclSpec ) {
313      buf.append(e.toString());
314      buf.append(",");
315    }
316    return buf.substring(0, buf.length()-1);  // remove last ,
317  }
318}