001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2008 Sun Microsystems, Inc.
026     */
027    
028    package org.opends.server.authorization.dseecompat;
029    import org.opends.messages.Message;
030    
031    import org.opends.server.types.ByteString;
032    import org.opends.server.types.DN;
033    import static org.opends.messages.AccessControlMessages.*;
034    import static org.opends.server.util.StaticUtils.isDigit;
035    
036    import java.util.regex.Pattern;
037    import java.util.HashSet;
038    
039    /**
040     * The Aci class represents ACI strings.
041     */
042    public class Aci  {
043    
044        /*
045         * The body of the ACI is the version, name and permission-bind rule
046         * pairs.
047         */
048        private AciBody body;
049    
050        /*
051         * The ACI targets.
052         */
053        private AciTargets targets=null;
054    
055        /**
056         * Version that we support.
057         */
058        public static final String supportedVersion="3.0";
059    
060        /*
061         * String representation of the ACI used.
062         */
063        private String aciString;
064    
065        /*
066         * The DN of the entry containing this ACI.
067         */
068        private final DN dn;
069    
070        /**
071         * Regular expression matching a word group.
072         */
073        public static final String WORD_GROUP="(\\w+)";
074    
075        /**
076         * Regular expression matching a word group at the start of a
077         * pattern.
078         */
079        public static final String WORD_GROUP_START_PATTERN = "^" + WORD_GROUP;
080    
081        /**
082         * Regular expression matching a white space.
083         */
084        public static final String ZERO_OR_MORE_WHITESPACE="\\s*";
085    
086        /**
087         * Regular expression matching a white space at the start of a pattern.
088         */
089        public static final String ZERO_OR_MORE_WHITESPACE_START_PATTERN =
090                                                 "^" + ZERO_OR_MORE_WHITESPACE ;
091    
092        /**
093         * Regular expression matching a white space at the end of a pattern.
094         */
095        private static final String ZERO_OR_MORE_WHITESPACE_END_PATTERN =
096                                                 ZERO_OR_MORE_WHITESPACE  + "$";
097    
098        /**
099         * Regular expression matching a ACL statement separator.
100         */
101        public static final String ACI_STATEMENT_SEPARATOR =
102                    ZERO_OR_MORE_WHITESPACE + ";" + ZERO_OR_MORE_WHITESPACE;
103    
104        /*
105         * This regular expression is used to do a quick syntax check
106         * when an ACI is being decoded.
107         */
108        private static final String aciRegex =
109               ZERO_OR_MORE_WHITESPACE_START_PATTERN + AciTargets.targetsRegex +
110               ZERO_OR_MORE_WHITESPACE + AciBody.bodyRegx +
111               ZERO_OR_MORE_WHITESPACE_END_PATTERN;
112    
113    
114        /**
115         * Regular expression that graciously matches an attribute type name. Must
116         * begin with an ASCII letter or digit, and contain only ASCII letters,
117         * digit characters, hyphens, semi-colons and underscores. It also allows
118         * the special shorthand characters "*" for all user attributes and "+" for
119         * all operational attributes.
120         */
121        public  static final String ATTR_NAME =
122                  "((?i)[a-z\\d]{1}[[a-z]\\d-_.;]*(?-i)|\\*{1}|\\+{1})";
123    
124        /**
125          * Regular expression matching a LDAP URL.
126          */
127         public  static final String LDAP_URL = ZERO_OR_MORE_WHITESPACE  +
128                                                     "(ldap:///[^\\|]+)";
129    
130        /**
131         *  String used to check for NULL ldap URL.
132         */
133         public static final String NULL_LDAP_URL = "ldap:///";
134    
135        /**
136         * Regular expression used to match token that joins expressions (||).
137         */
138        public static final String LOGICAL_OR = "\\|\\|";
139    
140        /**
141         * Regular expression used to match an open parenthesis.
142         */
143        public static final String OPEN_PAREN = "\\(";
144    
145        /**
146         * Regular expression used to match a closed parenthesis.
147         */
148        public static final String CLOSED_PAREN = "\\)";
149    
150        /**
151         * Regular expression used to match a single equal sign.
152         */
153        public static final String EQUAL_SIGN = "={1}";
154    
155        /**
156         * Regular expression the matches "*".
157         */
158        public static final String ALL_USER_ATTRS_WILD_CARD =
159                ZERO_OR_MORE_WHITESPACE +
160                        "\\*" + ZERO_OR_MORE_WHITESPACE;
161    
162        /**
163         * Regular expression the matches "+".
164         */
165        public static final String ALL_OP_ATTRS_WILD_CARD =
166                ZERO_OR_MORE_WHITESPACE +
167                        "\\+" + ZERO_OR_MORE_WHITESPACE;
168    
169        /*
170         * Regular expression used to do quick check of OID string.
171         */
172        private static final String OID_NAME = "[\\d.\\*]*";
173    
174        /*
175        * Regular expression that matches one or more OID_NAME's separated by
176        * the "||" token.
177        */
178        private static final String oidListRegex  =  ZERO_OR_MORE_WHITESPACE +
179                OID_NAME + ZERO_OR_MORE_WHITESPACE + "(" +
180                LOGICAL_OR + ZERO_OR_MORE_WHITESPACE + OID_NAME +
181                ZERO_OR_MORE_WHITESPACE + ")*";
182    
183        /**
184         * ACI_ADD is used to set the container rights for a LDAP add operation.
185         */
186        public static final int ACI_ADD = 0x0020;
187    
188        /**
189         * ACI_DELETE is used to set the container rights for a LDAP
190         * delete operation.
191         */
192        public static final int ACI_DELETE = 0x0010;
193    
194        /**
195         * ACI_READ is used to set the container rights for a LDAP
196         * search operation.
197         */
198        public static final int ACI_READ = 0x0004;
199    
200        /**
201         * ACI_WRITE is used to set the container rights for a LDAP
202         * modify operation.
203         */
204        public static final int ACI_WRITE = 0x0008;
205    
206        /**
207         * ACI_COMPARE is used to set the container rights for a LDAP
208         * compare operation.
209         */
210        public static final int ACI_COMPARE = 0x0001;
211    
212        /**
213         * ACI_SEARCH is used to set the container rights a LDAP search operation.
214         */
215        public static final int ACI_SEARCH = 0x0002;
216    
217        /**
218         * ACI_SELF is used for the SELFWRITE right.
219         */
220        public static final int ACI_SELF = 0x0040;
221    
222        /**
223         * ACI_ALL is used to as a mask for all of the above. These
224         * six below are not masked by the ACI_ALL.
225         */
226        public static final int ACI_ALL = 0x007F;
227    
228        /**
229         * ACI_PROXY is used for the PROXY right.
230         */
231        public static final int ACI_PROXY = 0x0080;
232    
233        /**
234         * ACI_IMPORT is used to set the container rights for a LDAP
235         * modify dn operation.
236         */
237        public static final int ACI_IMPORT = 0x0100;
238    
239        /**
240         * ACI_EXPORT is used to set the container rights for a LDAP
241         * modify dn operation.
242         */
243        public static final int ACI_EXPORT = 0x0200;
244    
245        /**
246         * ACI_WRITE_ADD is used by the LDAP modify operation.
247         */
248        public static final int ACI_WRITE_ADD = 0x800;
249    
250        /**
251         * ACI_WRITE_DELETE is used by the LDAP modify operation.
252         */
253        public static final int ACI_WRITE_DELETE = 0x400;
254    
255        /**
256         * ACI_SKIP_PROXY_CHECK is used to bypass the proxy access check.
257         */
258        public static final int ACI_SKIP_PROXY_CHECK = 0x400000;
259    
260        /**
261         * TARGATTRFILTER_ADD is used to specify that a
262         * targattrfilters ADD operation was seen in the ACI. For example,
263         * given an ACI with:
264         *
265         * (targattrfilters="add=mail:(mail=*@example.com)")
266         *
267         * The TARGATTRFILTERS_ADD flag would be set during ACI parsing in the
268         * TargAttrFilters class.
269         */
270        public static final int TARGATTRFILTERS_ADD = 0x1000;
271    
272        /**
273         * TARGATTRFILTER_DELETE is used to specify that a
274         * targattrfilters DELETE operation was seen in the ACI. For example,
275         * given an ACI with:
276         *
277         * (targattrfilters="del=mail:(mail=*@example.com)")
278         *
279         * The TARGATTRFILTERS_DELETE flag would be set during ACI parsing in the
280         * TargAttrFilters class.
281         */
282        public static final int TARGATTRFILTERS_DELETE = 0x2000;
283    
284        /**
285         * Used by the control evaluation access check.
286         */
287        public static final int ACI_CONTROL = 0x4000;
288    
289        /**
290         *  Used by the extended operation access check.
291         */
292        public static final int ACI_EXT_OP = 0x8000;
293    
294        /**
295         * ACI_ATTR_STAR_MATCHED is the flag set when the evaluation reason of a
296         * AciHandler.maysend ACI_READ access evaluation was the result of an
297         * ACI targetattr all attributes expression (targetattr="*") target match.
298         * For this flag to be set, there must be only one ACI matching.
299         *
300         * This flag and ACI_FOUND_ATTR_RULE are used in the
301         * AciHandler.filterEntry.accessAllowedAttrs method to skip access
302         * evaluation if the flag is ACI_ATTR_STAR_MATCHED (all attributes match)
303         * and the attribute type is not operational.
304         */
305        public static final int ACI_USER_ATTR_STAR_MATCHED = 0x0008;
306    
307        /**
308         * ACI_FOUND_USER_ATTR_RULE is the flag set when the evaluation reason of a
309         * AciHandler.maysend ACI_READ access evaluation was the result of an
310         * ACI targetattr specific user attribute expression
311         * (targetattr="some user attribute type") target match.
312         */
313        public static final int ACI_FOUND_USER_ATTR_RULE = 0x0010;
314    
315        /**
316         * ACI_OP_ATTR_PLUS_MATCHED is the flag set when the evaluation reason of a
317         * AciHandler.maysend ACI_READ access evaluation was the result of an
318         * ACI targetattr all operational attributes expression (targetattr="+")
319         * target match. For this flag to be set, there must be only one
320         * ACI matching.
321         *
322         * This flag and ACI_FOUND_OP_ATTR_RULE are used in the
323         * AciHandler.filterEntry.accessAllowedAttrs method to skip access
324         * evaluation if the flag is ACI_OP_ATTR_PLUS_MATCHED (all operational
325         * attributes match) and the attribute type is operational.
326         */
327    
328        public static final int ACI_OP_ATTR_PLUS_MATCHED = 0x0004;
329    
330        /**
331         * ACI_FOUND_OP_ATTR_RULE is the flag set when the evaluation reason of a
332         * AciHandler.maysend ACI_READ access evaluation was the result of an
333         * ACI targetattr specific operational attribute expression
334         * (targetattr="some operational attribute type") target match.
335         */
336        public static final int ACI_FOUND_OP_ATTR_RULE = 0x0020;
337    
338        /**
339         * ACI_NULL is used to set the container rights to all zeros. Used
340         * by LDAP modify.
341         */
342        public static final int ACI_NULL = 0x0000;
343    
344        /**
345         * Construct a new Aci from the provided arguments.
346         * @param input The string representation of the ACI.
347         * @param dn The DN of entry containing the ACI.
348         * @param body The body of the ACI.
349         * @param targets The targets of the ACI.
350         */
351        private  Aci(String input, DN dn, AciBody body, AciTargets targets) {
352            this.aciString  = input;
353            this.dn=dn;
354            this.body=body;
355            this.targets=targets;
356        }
357    
358        /**
359         * Decode an ACI byte string.
360         * @param byteString The ByteString containing the ACI string.
361         * @param dn DN of the ACI entry.
362         * @return  Returns a decoded ACI representing the string argument.
363         * @throws AciException If the parsing of the ACI string fails.
364         */
365        public static Aci decode (ByteString byteString, DN dn)
366        throws AciException {
367            String input=byteString.stringValue();
368            //Perform a quick pattern check against the string to catch any
369            //obvious syntax errors.
370            if (!Pattern.matches(aciRegex, input)) {
371                Message message = WARN_ACI_SYNTAX_GENERAL_PARSE_FAILED.get(input);
372                throw new AciException(message);
373            }
374            //Decode the body first.
375            AciBody body=AciBody.decode(input);
376            //Create a substring from the start of the string to start of
377            //the body. That should be the target.
378            String targetStr = input.substring(0, body.getMatcherStartPos());
379            //Decode that target string using the substring.
380            AciTargets targets=AciTargets.decode(targetStr, dn);
381            return new Aci(input, dn, body, targets);
382        }
383    
384        /**
385         * Return the string representation of the ACI. This was the string that
386         * was used to create the Aci class.
387         * @return A string representation of the ACI.
388         */
389        public String toString() {
390            return new String(aciString);
391        }
392    
393        /**
394         * Returns the targets of the ACI.
395         * @return Any AciTargets of the ACI. There may be no targets
396         * so this might be null.
397         */
398        public AciTargets getTargets() {
399            return targets;
400        }
401    
402        /**
403         * Return the DN of the entry containing the ACI.
404         * @return The DN of the entry containing the ACI.
405         */
406        public DN getDN() {
407            return dn;
408        }
409    
410        /**
411         * Test if the given ACI is applicable using the target match information
412         * provided. The ACI target can have seven keywords at this time:
413         *
414         * These two base decision on the resource entry DN:
415         *
416         *       1. target - checked in isTargetApplicable.
417         *       2. targetscope - checked in isTargetApplicable.
418         *
419         * These three base decision on resource entry attributes:
420         *
421         *       3. targetfilter - checked in isTargetFilterApplicable.
422         *       4. targetattr - checked in isTargetAttrApplicable.
423         *       5. targattrfilters -  checked in isTargAttrFiltersApplicable.
424         *
425         * These two base decisions on a resource entry built by the ACI handler
426         * that only contains a DN:
427         *       6. targetcontrol - check in isTargetControlApplicable.
428         *       7. extop - check in isExtOpApplicable.
429         *
430         * Six and seven are specific to the check being done: targetcontrol when a
431         * control is being evaluated and extop when an extended operation is
432         * evaluated.  None of the attribute based keywords should be checked
433         * when a control or extended op is being evaluated, because one
434         * of those attribute keywords rule might incorrectly make an ACI
435         * applicable that shouldn't be. This can happen by erroneously basing
436         * their decision on the ACI handler generated stub resource entry. For
437         * example, a "(targetattr != userpassword)" rule would match the generated
438         * stub resource entry, even though a control or extended op might be
439         * denied.
440         *
441         * What is allowed is the target and targetscope keywords, since the DN is
442         * known, so they are checked along with the correct method for the access
443         * check (isTargetControlApplicable for control and
444         * isTExtOpApplicable for extended operations). See comments in code
445         * where these checks are done.
446         *
447         * @param aci The ACI to test.
448         * @param matchCtx The target matching context containing all the info
449         * needed to match ACI targets.
450         * @return  True if this ACI targets are applicable or match.
451         */
452        public static boolean
453        isApplicable(Aci aci, AciTargetMatchContext matchCtx) {
454          if(matchCtx.hasRights(ACI_EXT_OP)) {
455            //Extended operation is being evaluated.
456             return AciTargets.isTargetApplicable(aci, matchCtx) &&
457                     AciTargets.isExtOpApplicable(aci, matchCtx);
458          } else if(matchCtx.hasRights(ACI_CONTROL)) {
459            //Control is being evaluated.
460             return AciTargets.isTargetApplicable(aci, matchCtx) &&
461                    AciTargets.isTargetControlApplicable(aci, matchCtx);
462          } else {
463            //If an ACI has extOp or targetControl targets skip it because the
464            //matchCtx right does not contain either ACI_EXT_OP or ACI_CONTROL at
465            //this point.
466            if(aci.getTargets().getExtOp() != null ||
467              (aci.getTargets().getTargetControl() != null)) {
468               return false;
469            } else {
470            int ctxRights = matchCtx.getRights();
471            //Check if the ACI and context have similar rights.
472            if(!aci.hasRights(ctxRights)) {
473              if(!(aci.hasRights(ACI_SEARCH| ACI_READ) &&
474                      matchCtx.hasRights(ACI_SEARCH | ACI_READ)))
475                return false;
476            }
477            return  AciTargets.isTargetApplicable(aci, matchCtx) &&
478                    AciTargets.isTargetFilterApplicable(aci, matchCtx) &&
479                    AciTargets.isTargAttrFiltersApplicable(aci, matchCtx) &&
480                    AciTargets.isTargetAttrApplicable(aci, matchCtx);
481          }
482          }
483        }
484    
485        /**
486         * Check if the body of the ACI matches the rights specified.
487         * @param rights Bit mask representing the rights to match.
488         * @return True if the body's rights match one of the rights specified.
489         */
490        public boolean hasRights(int rights) {
491            return body.hasRights(rights);
492        }
493    
494        /**
495         * Re-direct has access type to the body's hasAccessType method.
496         * @param accessType The access type to match.
497         * @return  True if the body's hasAccessType determines a permission
498         * contains this access type (allow or deny are valid types).
499         */
500        public boolean hasAccessType(EnumAccessType accessType) {
501            return body.hasAccessType(accessType);
502        }
503    
504        /**
505         * Evaluate this ACI using the evaluation context provided. Re-direct
506         * that calls the body's evaluate method.
507         * @param evalCtx The evaluation context to evaluate with.
508         * @return EnumEvalResult that contains the evaluation result of this
509         * aci evaluation.
510         */
511        private EnumEvalResult evaluate(AciEvalContext evalCtx) {
512            return body.evaluate(evalCtx);
513        }
514    
515        /**
516         * Static class used to evaluate an ACI and evaluation context.
517         * @param evalCtx  The context to evaluate with.
518         * @param aci The ACI to evaluate.
519         * @return EnumEvalResult that contains the evaluation result of the aci
520         * evaluation.
521         */
522        public static EnumEvalResult evaluate(AciEvalContext evalCtx, Aci aci) {
523            return aci.evaluate(evalCtx);
524        }
525    
526        /**
527         * Returns the name string of this ACI.
528         * @return The name string.
529         */
530        public String getName() {
531          return this.body.getName();
532        }
533    
534    
535      /**
536       *  Decode an OIDs expression string.
537       *
538       * @param expr A string representing the OID expression.
539       * @param msg  A message to be used if there is an exception.
540       *
541       * @return  Return a hash set of verfied OID strings parsed from the OID
542       *          expression.
543       *
544       * @throws AciException If the specified expression string is invalid.
545       */
546    
547        public static HashSet<String> decodeOID(String expr, Message msg)
548        throws AciException {
549          HashSet<String> OIDs = new HashSet<String>();
550          //Quick check to see if the expression is valid.
551          if (Pattern.matches(oidListRegex, expr)) {
552            // Remove the spaces in the oid string and
553            // split the list.
554            Pattern separatorPattern =
555                    Pattern.compile(LOGICAL_OR);
556            String oidString =
557                    expr.replaceAll(ZERO_OR_MORE_WHITESPACE, "");
558            String[] oidArray=
559                    separatorPattern.split(oidString);
560            //More careful analysis of each OID string.
561            for(String oid : oidArray) {
562              verifyOid(oid);
563              OIDs.add(oid);
564            }
565          } else {
566            throw new AciException(msg);
567          }
568          return OIDs;
569        }
570    
571        /**
572         *  Verfiy the specified OID string.
573         *
574         * @param oidStr The string representing an OID.
575         *
576         * @throws AciException If the specified string is invalid.
577         */
578        private static void verifyOid(String oidStr) throws AciException {
579          int pos=0, length=oidStr.length();
580          char c;
581          if(oidStr.equals("*"))
582            return;
583          boolean lastWasPeriod = false;
584          while ((pos < length) && ((c = oidStr.charAt(pos++)) != ' ')) {
585            if (c == '.') {
586              if (lastWasPeriod) {
587                Message message = WARN_ACI_SYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(
588                    oidStr, pos-1);
589                throw new AciException(message);
590              }  else
591                lastWasPeriod = true;
592            }  else if (! isDigit(c)) {
593              Message message =
594                  WARN_ACI_SYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(oidStr, c, pos-1);
595              throw new AciException(message);
596            }  else
597              lastWasPeriod = false;
598          }
599        }
600      }