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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.authorization.dseecompat;
028
029import org.forgerock.i18n.LocalizableMessage;
030
031import static org.opends.messages.AccessControlMessages.*;
032import static org.opends.server.authorization.dseecompat.Aci.*;
033import java.util.StringTokenizer;
034import java.util.LinkedHashSet;
035import java.util.regex.Pattern;
036import java.util.regex.Matcher;
037
038import org.opends.server.core.DirectoryServer;
039import org.opends.server.types.AttributeType;
040import org.opends.server.types.DN;
041import org.opends.server.types.LDAPURL;
042import org.opends.server.types.DirectoryException;
043
044/**
045 * This class is used by USERDN and GROUPDN userattr types
046 * to determine what parent inheritance checks to make.
047 */
048public class ParentInheritance {
049
050    /** The maximum number of parent inheritance levels supported. */
051    private static final int MAX_LEVELS=10;
052
053    /** Pattern to match for parent inheritance. */
054    private final String parentPat="parent[";
055
056    /**
057     * Array used to hold the level information. Each slot corresponds to a
058     * level parsed from the rule.
059     */
060    private final int[] levels=new int[MAX_LEVELS];
061
062    /** The number of levels parsed. */
063    private int numLevels;
064
065    /**
066     * The attribute type string parsed from the rule. Only used in
067     * inheritance search.
068     */
069    private String attrTypeStr;
070
071    /**
072     * The base DN of a URL parsed from the rule. Used to make sure groupdn
073     * are under this suffix. Originally a way to search all nested groups
074     * under this suffix, so the behavior is slightly different.
075     */
076    private DN baseDN;
077
078
079    /**
080     * Construct a class from the inheritance pattern. The skipParsing boolean
081     * specifies that parent parsing should be skipped and sets up the class:
082     * with numLevels=1, level[0]=0 and an attribute type from the
083     * specified pattern.
084     *
085     * @param pattern The string pattern containing the inheritance
086     * information.
087     * @param skipParse Specify if the parent inheritance parsing should be
088     * skipped or not.
089     * @throws AciException  If the pattern is invalid.
090     */
091    ParentInheritance (String pattern, boolean skipParse)  throws AciException {
092        if (skipParse) {
093            //The "parent[" pattern is invalid for ROLEDN user attr keyword.
094            if(pattern.startsWith(parentPat)) {
095                LocalizableMessage message =
096                  WARN_ACI_SYNTAX_INVALID_USERATTR_ROLEDN_INHERITANCE_PATTERN
097                          .get(pattern);
098                throw new AciException(message);
099            }  else {
100                pattern=pattern.trim();
101                Pattern pattern1=Pattern.compile(ATTR_NAME);
102                Matcher matcher=pattern1.matcher(pattern);
103               //Check if valid attribute type name.
104               if(!matcher.find() || matcher.groupCount() != 1) {
105                LocalizableMessage message =
106                    WARN_ACI_SYNTAX_INVALID_ATTRIBUTE_TYPE_NAME.get(pattern);
107                throw new AciException(message);
108               }
109               numLevels=1;
110              levels[0]=0;
111            }
112    } else {
113      parse(pattern);
114    }
115}
116
117    /**
118     * Performs all parsing of the specified pattern string.
119     * @param pattern The string pattern containing the inheritance
120     * information.
121     * @throws AciException  If the pattern is invalid.
122     */
123    private void parse (String pattern) throws AciException {
124        pattern=pattern.trim();
125        // Check if we have a "parent[" string.
126        if(pattern.startsWith(parentPat)) {
127            numLevels=0;
128            levels[0]=0;
129            String p=pattern.substring(parentPat.length());
130            /*
131             * Format needs to be parent[XX].attribute -- everything after the
132             * '.' is the attribute type.
133             */
134            String[] toks=p.split("\\.");
135            if(toks.length != 2) {
136                LocalizableMessage message =
137                  WARN_ACI_SYNTAX_INVALID_USERATTR_INHERITANCE_PATTERN
138                          .get(pattern);
139                throw new AciException(message);
140            }
141            Pattern pattern1=Pattern.compile(ATTR_NAME);
142            Matcher matcher=pattern1.matcher(toks[1]);
143            //Check if valid attribute type name.
144            if(!matcher.find() || matcher.groupCount() != 1) {
145                LocalizableMessage message =
146                    WARN_ACI_SYNTAX_INVALID_ATTRIBUTE_TYPE_NAME.get(toks[1]);
147                throw new AciException(message);
148            }
149            attrTypeStr=toks[1];
150            StringTokenizer tok=new StringTokenizer(toks[0],"],",false);
151            while(tok.hasMoreTokens()) {
152                String v=tok.nextToken();
153                /*
154                 * Everything between the brackets must be an integer or it's
155                 * an error.
156                 */
157                try {
158                    if(numLevels < MAX_LEVELS) {
159                        levels[numLevels++]=Integer.decode(v);
160                    } else {
161                        LocalizableMessage message =
162                        WARN_ACI_SYNTAX_MAX_USERATTR_INHERITANCE_LEVEL_EXCEEDED.get(pattern, MAX_LEVELS);
163                        throw new AciException(message);
164                    }
165                } catch (NumberFormatException ex) {
166                    LocalizableMessage message =
167                        WARN_ACI_SYNTAX_INVALID_INHERITANCE_VALUE.get(pattern);
168                    throw new AciException(message);
169                }
170            }
171        } else {
172          attrTypeStr=pattern;
173          if(pattern.startsWith(NULL_LDAP_URL)) {
174            try {
175              LDAPURL url=LDAPURL.decode(pattern, true);
176              LinkedHashSet<String>attrs=url.getAttributes();
177              if(attrs.size() != 1) {
178                LocalizableMessage message =
179                    WARN_ACI_SYNTAX_INVALID_USERATTR_ATTR_URL.get(pattern);
180                throw new AciException(message);
181              }
182              baseDN=url.getBaseDN();
183              if(baseDN.isRootDN()){
184                LocalizableMessage message =
185                    WARN_ACI_SYNTAX_INVALID_USERATTR_BASEDN_URL.get(pattern);
186                throw new AciException(message);
187              }
188              attrTypeStr=attrs.iterator().next();
189            } catch (DirectoryException ex) {
190              LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_USERATTR_URL.get(
191                  ex.getMessageObject());
192              throw new AciException(message);
193            }
194          }
195          numLevels=1;
196          levels[0]=0;
197        }
198    }
199
200    /**
201     * Returns the number of levels counted.
202     * @return The number of levels.
203     */
204    public int getNumLevels() {
205        return numLevels;
206    }
207
208    /**
209     * Returns an array of levels, where levels are integers.
210     * @return Return an array of levels.
211     */
212    public int[] getLevels() {
213        int[] levelsCopy = new int[levels.length];
214        System.arraycopy(levels, 0, levelsCopy, 0, levels.length);
215        return levelsCopy;
216    }
217
218    /**
219     * Return the attribute type.
220     * @return The attribute type.
221     */
222    public AttributeType getAttributeType() {
223      return DirectoryServer.getAttributeTypeOrDefault(attrTypeStr.toLowerCase());
224    }
225
226    /**
227     * Return the string representation of the attribute type.
228     * @return   The attribute type string.
229     */
230    public String getAttrTypeStr() {
231        return attrTypeStr;
232    }
233
234  /**
235   * Return the DN that groupdn must be under.
236   *
237   * @return DN that groupdn must be under.
238   */
239  public DN getBaseDN() {
240      return baseDN;
241    }
242}
243