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-2024 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}