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.List;
021
022import org.apache.hadoop.classification.InterfaceAudience;
023import org.apache.hadoop.classification.InterfaceStability;
024
025import com.google.common.base.Objects;
026import com.google.common.base.Preconditions;
027import com.google.common.collect.Lists;
028
029/**
030 * An AclStatus contains the ACL information of a specific file. AclStatus
031 * instances are immutable. Use a {@link Builder} to create a new instance.
032 */
033@InterfaceAudience.Public
034@InterfaceStability.Stable
035public class AclStatus {
036  private final String owner;
037  private final String group;
038  private final boolean stickyBit;
039  private final List<AclEntry> entries;
040  private final FsPermission permission;
041
042  /**
043   * Returns the file owner.
044   *
045   * @return String file owner
046   */
047  public String getOwner() {
048    return owner;
049  }
050
051  /**
052   * Returns the file group.
053   *
054   * @return String file group
055   */
056  public String getGroup() {
057    return group;
058  }
059
060  /**
061   * Returns the sticky bit.
062   * 
063   * @return boolean sticky bit
064   */
065  public boolean isStickyBit() {
066    return stickyBit;
067  }
068
069  /**
070   * Returns the list of all ACL entries, ordered by their natural ordering.
071   *
072   * @return List<AclEntry> unmodifiable ordered list of all ACL entries
073   */
074  public List<AclEntry> getEntries() {
075    return entries;
076  }
077
078  /**
079   * Returns the permission set for the path
080   * @return {@link FsPermission} for the path
081   */
082  public FsPermission getPermission() {
083    return permission;
084  }
085
086  @Override
087  public boolean equals(Object o) {
088    if (o == null) {
089      return false;
090    }
091    if (getClass() != o.getClass()) {
092      return false;
093    }
094    AclStatus other = (AclStatus)o;
095    return Objects.equal(owner, other.owner)
096        && Objects.equal(group, other.group)
097        && stickyBit == other.stickyBit
098        && Objects.equal(entries, other.entries);
099  }
100
101  @Override
102  public int hashCode() {
103    return Objects.hashCode(owner, group, stickyBit, entries);
104  }
105
106  @Override
107  public String toString() {
108    return new StringBuilder()
109      .append("owner: ").append(owner)
110      .append(", group: ").append(group)
111      .append(", acl: {")
112      .append("entries: ").append(entries)
113      .append(", stickyBit: ").append(stickyBit)
114      .append('}')
115      .toString();
116  }
117
118  /**
119   * Builder for creating new Acl instances.
120   */
121  public static class Builder {
122    private String owner;
123    private String group;
124    private boolean stickyBit;
125    private List<AclEntry> entries = Lists.newArrayList();
126    private FsPermission permission = null;
127
128    /**
129     * Sets the file owner.
130     *
131     * @param owner String file owner
132     * @return Builder this builder, for call chaining
133     */
134    public Builder owner(String owner) {
135      this.owner = owner;
136      return this;
137    }
138
139    /**
140     * Sets the file group.
141     *
142     * @param group String file group
143     * @return Builder this builder, for call chaining
144     */
145    public Builder group(String group) {
146      this.group = group;
147      return this;
148    }
149
150    /**
151     * Adds an ACL entry.
152     *
153     * @param e AclEntry entry to add
154     * @return Builder this builder, for call chaining
155     */
156    public Builder addEntry(AclEntry e) {
157      this.entries.add(e);
158      return this;
159    }
160
161    /**
162     * Adds a list of ACL entries.
163     *
164     * @param entries AclEntry entries to add
165     * @return Builder this builder, for call chaining
166     */
167    public Builder addEntries(Iterable<AclEntry> entries) {
168      for (AclEntry e : entries)
169        this.entries.add(e);
170      return this;
171    }
172
173    /**
174     * Sets sticky bit. If this method is not called, then the builder assumes
175     * false.
176     *
177     * @param stickyBit
178     *          boolean sticky bit
179     * @return Builder this builder, for call chaining
180     */
181    public Builder stickyBit(boolean stickyBit) {
182      this.stickyBit = stickyBit;
183      return this;
184    }
185
186    /**
187     * Sets the permission for the file.
188     * @param permission
189     */
190    public Builder setPermission(FsPermission permission) {
191      this.permission = permission;
192      return this;
193    }
194
195    /**
196     * Builds a new AclStatus populated with the set properties.
197     *
198     * @return AclStatus new AclStatus
199     */
200    public AclStatus build() {
201      return new AclStatus(owner, group, stickyBit, entries, permission);
202    }
203  }
204
205  /**
206   * Private constructor.
207   *
208   * @param file Path file associated to this ACL
209   * @param owner String file owner
210   * @param group String file group
211   * @param stickyBit the sticky bit
212   * @param entries the ACL entries
213   * @param permission permission of the path
214   */
215  private AclStatus(String owner, String group, boolean stickyBit,
216      Iterable<AclEntry> entries, FsPermission permission) {
217    this.owner = owner;
218    this.group = group;
219    this.stickyBit = stickyBit;
220    this.entries = Lists.newArrayList(entries);
221    this.permission = permission;
222  }
223
224  /**
225   * Get the effective permission for the AclEntry
226   * @param entry AclEntry to get the effective action
227   */
228  public FsAction getEffectivePermission(AclEntry entry) {
229    return getEffectivePermission(entry, permission);
230  }
231
232  /**
233   * Get the effective permission for the AclEntry. <br>
234   * Recommended to use this API ONLY if client communicates with the old
235   * NameNode, needs to pass the Permission for the path to get effective
236   * permission, else use {@link AclStatus#getEffectivePermission(AclEntry)}.
237   * @param entry AclEntry to get the effective action
238   * @param permArg Permission for the path. However if the client is NOT
239   *          communicating with old namenode, then this argument will not have
240   *          any preference.
241   * @return Returns the effective permission for the entry.
242   * @throws IllegalArgumentException If the client communicating with old
243   *           namenode and permission is not passed as an argument.
244   */
245  public FsAction getEffectivePermission(AclEntry entry, FsPermission permArg)
246      throws IllegalArgumentException {
247    // At least one permission bits should be available.
248    Preconditions.checkArgument(this.permission != null || permArg != null,
249        "Permission bits are not available to calculate effective permission");
250    if (this.permission != null) {
251      // permission bits from server response will have the priority for
252      // accuracy.
253      permArg = this.permission;
254    }
255    if ((entry.getName() != null || entry.getType() == AclEntryType.GROUP)) {
256      if (entry.getScope() == AclEntryScope.ACCESS) {
257        FsAction entryPerm = entry.getPermission();
258        return entryPerm.and(permArg.getGroupAction());
259      } else {
260        Preconditions.checkArgument(this.entries.contains(entry)
261            && this.entries.size() >= 3,
262            "Passed default ACL entry not found in the list of ACLs");
263        // default mask entry for effective permission calculation will be the
264        // penultimate entry. This can be mask entry in case of extended ACLs.
265        // In case of minimal ACL, this is the owner group entry, and we end up
266        // intersecting group FsAction with itself, which is a no-op.
267        FsAction defaultMask = this.entries.get(this.entries.size() - 2)
268            .getPermission();
269        FsAction entryPerm = entry.getPermission();
270        return entryPerm.and(defaultMask);
271      }
272    } else {
273      return entry.getPermission();
274    }
275  }
276}