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    package org.apache.hadoop.fs.permission;
019    
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.List;
023    
024    import com.google.common.base.Objects;
025    
026    import org.apache.hadoop.HadoopIllegalArgumentException;
027    import org.apache.hadoop.classification.InterfaceAudience;
028    import org.apache.hadoop.classification.InterfaceStability;
029    import 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
040    public 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    }