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 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.schema;
028import static org.opends.messages.SchemaMessages.*;
029import static org.opends.server.schema.SchemaConstants.*;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.opendj.ldap.ByteSequence;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.Schema;
035import org.forgerock.opendj.ldap.schema.Syntax;
036import org.opends.server.admin.std.server.AttributeSyntaxCfg;
037import org.opends.server.api.AttributeSyntax;
038import org.opends.server.types.DirectoryException;
039
040/**
041 * This class defines the auth password attribute syntax, which is defined in
042 * RFC 3112 and is used to hold authentication information.  Only equality
043 * matching will be allowed by default.
044 */
045public class AuthPasswordSyntax
046       extends AttributeSyntax<AttributeSyntaxCfg>
047{
048
049  /**
050   * Creates a new instance of this syntax.  Note that the only thing that
051   * should be done here is to invoke the default constructor for the
052   * superclass.  All initialization should be performed in the
053   * <CODE>initializeSyntax</CODE> method.
054   */
055  public AuthPasswordSyntax()
056  {
057    super();
058  }
059
060  @Override
061  public Syntax getSDKSyntax(Schema schema)
062  {
063    return schema.getSyntax(SchemaConstants.SYNTAX_AUTH_PASSWORD_OID);
064  }
065
066  /**
067   * Retrieves the common name for this attribute syntax.
068   *
069   * @return  The common name for this attribute syntax.
070   */
071  @Override
072  public String getName()
073  {
074    return SYNTAX_AUTH_PASSWORD_NAME;
075  }
076
077  /**
078   * Retrieves the OID for this attribute syntax.
079   *
080   * @return  The OID for this attribute syntax.
081   */
082  @Override
083  public String getOID()
084  {
085    return SYNTAX_AUTH_PASSWORD_OID;
086  }
087
088  /**
089   * Retrieves a description for this attribute syntax.
090   *
091   * @return  A description for this attribute syntax.
092   */
093  @Override
094  public String getDescription()
095  {
096    return SYNTAX_AUTH_PASSWORD_DESCRIPTION;
097  }
098
099  /**
100   * Decodes the provided authentication password value into its component parts.
101   * <p>
102   * FIXME this is a duplicate of {@link org.forgerock.opendj.ldap.schema.AuthPasswordSyntaxImplTest}
103   *
104   * @param  authPasswordValue  The authentication password value to be decoded.
105   * @return  A three-element array, containing the scheme, authInfo, and
106   *          authValue components of the given string, in that order.
107   * @throws  DirectoryException  If a problem is encountered while attempting
108   *                              to decode the value.
109   */
110  public static String[] decodeAuthPassword(String authPasswordValue) throws DirectoryException
111  {
112    // Create placeholders for the values to return.
113    StringBuilder scheme    = new StringBuilder();
114    StringBuilder authInfo  = new StringBuilder();
115    StringBuilder authValue = new StringBuilder();
116
117
118    // First, ignore any leading whitespace.
119    int length = authPasswordValue.length();
120    int  pos   = 0;
121    while (pos < length && authPasswordValue.charAt(pos) == ' ')
122    {
123      pos++;
124    }
125
126
127    // The next set of characters will be the scheme, which must consist only
128    // of digits, uppercase alphabetic characters, dash, period, slash, and
129    // underscore characters.  It must be immediately followed by one or more
130    // spaces or a dollar sign.
131readScheme:
132    while (pos < length)
133    {
134      char c = authPasswordValue.charAt(pos);
135
136      switch (c)
137      {
138        case '0':
139        case '1':
140        case '2':
141        case '3':
142        case '4':
143        case '5':
144        case '6':
145        case '7':
146        case '8':
147        case '9':
148        case 'A':
149        case 'B':
150        case 'C':
151        case 'D':
152        case 'E':
153        case 'F':
154        case 'G':
155        case 'H':
156        case 'I':
157        case 'J':
158        case 'K':
159        case 'L':
160        case 'M':
161        case 'N':
162        case 'O':
163        case 'P':
164        case 'Q':
165        case 'R':
166        case 'S':
167        case 'T':
168        case 'U':
169        case 'V':
170        case 'W':
171        case 'X':
172        case 'Y':
173        case 'Z':
174        case '-':
175        case '.':
176        case '/':
177        case '_':
178          scheme.append(c);
179          pos++;
180          break;
181        case ' ':
182        case '$':
183          break readScheme;
184        default:
185          LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
186          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
187                                       message);
188      }
189    }
190
191
192    // The scheme must consist of at least one character.
193    if (scheme.length() == 0)
194    {
195      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
196      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
197              message);
198    }
199
200
201    // Ignore any spaces before the dollar sign separator.  Then read the dollar
202    // sign and ignore any trailing spaces.
203    while (pos < length && authPasswordValue.charAt(pos) == ' ')
204    {
205      pos++;
206    }
207
208    if (pos < length && authPasswordValue.charAt(pos) == '$')
209    {
210      pos++;
211    }
212    else
213    {
214      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
215      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
216              message);
217    }
218
219    while (pos < length && authPasswordValue.charAt(pos) == ' ')
220    {
221      pos++;
222    }
223
224
225    // The next component must be the authInfo element, containing only
226    // printable characters other than the dollar sign and space character.
227readAuthInfo:
228    while (pos < length)
229    {
230      char c = authPasswordValue.charAt(pos);
231      if (c == ' ' || c == '$')
232      {
233        break readAuthInfo;
234      }
235      else if (PrintableString.isPrintableCharacter(c))
236      {
237        authInfo.append(c);
238        pos++;
239      }
240      else
241      {
242        LocalizableMessage message =
243            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
244        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
245                                     message);
246      }
247    }
248
249
250    // The authInfo element must consist of at least one character.
251    if (authInfo.length() == 0)
252    {
253      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
254      throw new DirectoryException(
255              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
256    }
257
258
259    // Ignore any spaces before the dollar sign separator.  Then read the dollar
260    // sign and ignore any trailing spaces.
261    while (pos < length && authPasswordValue.charAt(pos) == ' ')
262    {
263      pos++;
264    }
265
266    if (pos < length && authPasswordValue.charAt(pos) == '$')
267    {
268      pos++;
269    }
270    else
271    {
272      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
273      throw new DirectoryException(
274              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
275    }
276
277    while (pos < length && authPasswordValue.charAt(pos) == ' ')
278    {
279      pos++;
280    }
281
282
283    // The final component must be the authValue element, containing only
284    // printable characters other than the dollar sign and space character.
285    while (pos < length)
286    {
287      char c = authPasswordValue.charAt(pos);
288      if (c == ' ' || c == '$')
289      {
290        break ;
291      }
292      else if (PrintableString.isPrintableCharacter(c))
293      {
294        authValue.append(c);
295        pos++;
296      }
297      else
298      {
299        LocalizableMessage message =
300            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
301        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
302                                     message);
303      }
304    }
305
306
307    // The authValue element must consist of at least one character.
308    if (authValue.length() == 0)
309    {
310      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
311      throw new DirectoryException(
312              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
313    }
314
315
316    // The only characters remaining must be whitespace.
317    while (pos < length)
318    {
319      char c = authPasswordValue.charAt(pos);
320      if (c == ' ')
321      {
322        pos++;
323      }
324      else
325      {
326        LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
327        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
328                                     message);
329      }
330    }
331
332
333    // If we've gotten here, then everything must be OK.
334    return new String[]
335    {
336      scheme.toString(),
337      authInfo.toString(),
338      authValue.toString()
339    };
340  }
341
342  /**
343   * Indicates whether the provided value is encoded using the auth password
344   * syntax.
345   *
346   * @param  value  The value for which to make the determination.
347   *
348   * @return  <CODE>true</CODE> if the value appears to be encoded using the
349   *          auth password syntax, or <CODE>false</CODE> if not.
350   */
351  public static boolean isEncoded(ByteSequence value)
352  {
353    // FIXME -- Make this more efficient, and don't use exceptions for flow control.
354    try
355    {
356      decodeAuthPassword(value.toString());
357      return true;
358    }
359    catch (Exception e)
360    {
361      return false;
362    }
363  }
364}
365