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