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 * docs/licenses/cddl.txt
011 * or http://www.opensource.org/licenses/cddl1.php.
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 * docs/licenses/cddl.txt.  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 *      Portions Copyright 2010-2023 Ping Identity Corporation
026 */
027package com.unboundid.directory.sdk.ds.scripting;
028
029
030
031import java.security.cert.Certificate;
032import java.util.List;
033
034import com.unboundid.directory.sdk.common.internal.Reconfigurable;
035import com.unboundid.directory.sdk.ds.config.CertificateMapperConfig;
036import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
037import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
038import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
039import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.util.Extensible;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045import com.unboundid.util.args.ArgumentException;
046import com.unboundid.util.args.ArgumentParser;
047
048
049
050/**
051 * This class defines an API that must be implemented by scripted extensions
052 * which attempt to map a certificate presented by a client (e.g., via SSL or
053 * StartTLS) to a user defined in the server.  This is primarily used during
054 * the course of SASL EXTERNAL bind processing when a client uses a certificate
055 * to authenticate to the server.  Any information contained in the provided
056 * certificate chain (including the subject, fingerprint, validity, extensions,
057 * etc. of the client certificate, as well as any information about any issuer
058 * certificates) may be used to map information in the provided certificate
059 * chain to exactly one user in the server.  If the certificate cannot be mapped
060 * to any user, or if it is mapped to multiple users, then the authentication
061 * attempt must fail.
062 * <BR>
063 * <H2>Configuring Groovy-Scripted Certificate Mappers</H2>
064 * In order to configure a scripted certificate mapper based on this API and
065 * written in the Groovy scripting language, use a command like:
066 * <PRE>
067 *      dsconfig create-certificate-mapper \
068 *           --mapper-name "<I>{mapper-name}</I>" \
069 *           --type groovy-scripted \
070 *           --set enabled:true \
071 *           --set "script-class:<I>{class-name}</I>" \
072 *           --set "script-argument:<I>{name=value}</I>"
073 * </PRE>
074 * where "<I>{mapper-name}</I>" is the name to use for the certificate mapper
075 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Groovy
076 * class written using this API, and "<I>{name=value}</I>" represents name-value
077 * pairs for any arguments to provide to the certificate mapper.  If multiple
078 * arguments should be provided to the certificate mapper, then the
079 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
080 * provided multiple times.
081 *
082 * @see  com.unboundid.directory.sdk.ds.api.CertificateMapper
083 */
084@Extensible()
085@DirectoryServerExtension()
086@DirectoryProxyServerExtension(appliesToLocalContent=true,
087     appliesToRemoteContent=false)
088@SynchronizationServerExtension(appliesToLocalContent=true,
089     appliesToSynchronizedContent=false)
090@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
091public abstract class ScriptedCertificateMapper
092       implements Reconfigurable<CertificateMapperConfig>
093{
094  /**
095   * Creates a new instance of this certificate mapper.  All certificate mapper
096   * implementations must include a default constructor, but any initialization
097   * should generally be done in the {@code initializeCertificateMapper} method.
098   */
099  public ScriptedCertificateMapper()
100  {
101    // No implementation is required.
102  }
103
104
105
106  /**
107   * {@inheritDoc}
108   */
109  public void defineConfigArguments(final ArgumentParser parser)
110         throws ArgumentException
111  {
112    // No arguments will be allowed by default.
113  }
114
115
116
117  /**
118   * Initializes this certificate mapper.
119   *
120   * @param  serverContext  A handle to the server context for the server in
121   *                        which this extension is running.
122   * @param  config         The general configuration for this certificate
123   *                        mapper.
124   * @param  parser         The argument parser which has been initialized from
125   *                        the configuration for this certificate mapper.
126   *
127   * @throws  LDAPException  If a problem occurs while initializing this
128   *                         certificate mapper.
129   */
130  public void initializeCertificateMapper(
131                   final DirectoryServerContext serverContext,
132                   final CertificateMapperConfig config,
133                   final ArgumentParser parser)
134         throws LDAPException
135  {
136    // No initialization will be performed by default.
137  }
138
139
140
141  /**
142   * {@inheritDoc}
143   */
144  public boolean isConfigurationAcceptable(final CertificateMapperConfig config,
145                      final ArgumentParser parser,
146                      final List<String> unacceptableReasons)
147  {
148    // No extended validation will be performed.
149    return true;
150  }
151
152
153
154  /**
155   * {@inheritDoc}
156   */
157  public ResultCode applyConfiguration(final CertificateMapperConfig config,
158                                       final ArgumentParser parser,
159                                       final List<String> adminActionsRequired,
160                                       final List<String> messages)
161  {
162    // By default, no configuration changes will be applied.  If there are any
163    // arguments, then add an admin action message indicating that the extension
164    // needs to be restarted for any changes to take effect.
165    if (! parser.getNamedArguments().isEmpty())
166    {
167      adminActionsRequired.add(
168           "No configuration change has actually been applied.  The new " +
169                "configuration will not take effect until this certificate " +
170                "mapper is disabled and re-enabled or until the server is " +
171                "restarted.");
172    }
173
174    return ResultCode.SUCCESS;
175  }
176
177
178
179  /**
180   * Performs any cleanup which may be necessary when this certificate mapper is
181   * to be taken out of service.
182   */
183  public void finalizeCertificateMapper()
184  {
185    // No implementation is required.
186  }
187
188
189
190  /**
191   * Performs any processing which may be necessary to map the provided
192   * certificate chain to a user within the server.
193   *
194   * @param  certChain  The certificate chain presented by the client.
195   *
196   * @return  The DN of the user within the server to which the provided
197   *          certificate corresponds.
198   *
199   * @throws  LDAPException  If the presented certificate cannot be mapped to
200   *                         exactly one user in the server.
201   */
202  public abstract String mapCertificate(final Certificate[] certChain)
203         throws LDAPException;
204}