Skip to content

Commit 77cc2b9

Browse files
JAMES-4210 Allow IMAP SASL provider extensions from configuration
Load extra IMAP SASL authentication service factory providers from auth.saslAuthenticationServiceFactoryProviderExtensions. Providers are Guice-instantiated, merged with built-in providers, and can parse their own IMAP auth configuration.
1 parent 52573c6 commit 77cc2b9

2 files changed

Lines changed: 92 additions & 5 deletions

File tree

server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
import com.google.inject.multibindings.ProvidesIntoSet;
104104

105105
public class IMAPServerModule extends AbstractModule {
106+
private static final String SASL_AUTHENTICATION_SERVICE_FACTORY_PROVIDER_EXTENSIONS = "auth.saslAuthenticationServiceFactoryProviderExtensions";
107+
106108
private static Stream<Pair<Class, AbstractProcessor>> asPairStream(AbstractProcessor p) {
107109
return p.acceptableClasses()
108110
.stream().map(clazz -> Pair.of(clazz, p));
@@ -208,25 +210,59 @@ private ImapPackage retrievePackages(GuiceLoader guiceLoader, HierarchicalConfig
208210
}
209211

210212
private SaslMechanismRegistry retrieveSaslMechanisms(SaslMechanismLoader saslMechanismLoader,
213+
GuiceLoader guiceLoader,
211214
Set<ImapSaslAuthenticationServiceFactoryProvider> saslAuthenticationServiceFactoryProviders,
212215
DefaultImapSaslMechanismClassNamesProvider defaultImapSaslMechanismClassNamesProvider,
213216
HierarchicalConfiguration<ImmutableNode> configuration) throws ConfigurationException {
214217
ImmutableList<String> mechanismClassNames = retrieveSaslMechanismClassNames(configuration, defaultImapSaslMechanismClassNamesProvider);
215218
ImmutableList<SaslMechanism> mechanisms = saslMechanismLoader.load(mechanismClassNames);
216219
ImmutableList<SaslAuthenticationServiceFactory<?>> saslAuthenticationServiceFactories =
217-
retrieveSaslAuthenticationServiceFactories(configuration, saslAuthenticationServiceFactoryProviders);
220+
retrieveSaslAuthenticationServiceFactories(configuration, guiceLoader, saslAuthenticationServiceFactoryProviders);
218221
return new SaslMechanismRegistry(mechanisms, saslAuthenticationServiceFactories);
219222
}
220223

221224
ImmutableList<SaslAuthenticationServiceFactory<?>> retrieveSaslAuthenticationServiceFactories(HierarchicalConfiguration<ImmutableNode> configuration,
225+
GuiceLoader guiceLoader,
222226
Set<ImapSaslAuthenticationServiceFactoryProvider> providers) throws ConfigurationException {
223227
ImmutableList.Builder<SaslAuthenticationServiceFactory<?>> factories = ImmutableList.builder();
224-
for (ImapSaslAuthenticationServiceFactoryProvider provider : providers) {
228+
ImmutableList<ImapSaslAuthenticationServiceFactoryProvider> allProviders = ImmutableList.<ImapSaslAuthenticationServiceFactoryProvider>builder()
229+
.addAll(providers)
230+
.addAll(retrieveConfiguredSaslAuthenticationServiceFactoryProviders(configuration, guiceLoader))
231+
.build();
232+
233+
for (ImapSaslAuthenticationServiceFactoryProvider provider : allProviders) {
225234
factories.addAll(provider.provide(configuration));
226235
}
227236
return factories.build();
228237
}
229238

239+
ImmutableList<ImapSaslAuthenticationServiceFactoryProvider> retrieveConfiguredSaslAuthenticationServiceFactoryProviders(HierarchicalConfiguration<ImmutableNode> configuration,
240+
GuiceLoader guiceLoader) throws ConfigurationException {
241+
if (!configuration.containsKey(SASL_AUTHENTICATION_SERVICE_FACTORY_PROVIDER_EXTENSIONS)) {
242+
return ImmutableList.of();
243+
}
244+
245+
ImmutableList<String> providerClassNames = Arrays.stream(configuration.getStringArray(SASL_AUTHENTICATION_SERVICE_FACTORY_PROVIDER_EXTENSIONS))
246+
.flatMap(value -> Arrays.stream(value.split(",")))
247+
.map(String::trim)
248+
.collect(ImmutableList.toImmutableList());
249+
250+
if (providerClassNames.isEmpty() || providerClassNames.stream().anyMatch(StringUtils::isBlank)) {
251+
throw new ConfigurationException(SASL_AUTHENTICATION_SERVICE_FACTORY_PROVIDER_EXTENSIONS + " must not be blank when configured");
252+
}
253+
254+
ImmutableList.Builder<ImapSaslAuthenticationServiceFactoryProvider> providers = ImmutableList.builder();
255+
for (String providerClassName : providerClassNames) {
256+
try {
257+
ImapSaslAuthenticationServiceFactoryProvider provider = guiceLoader.instantiate(new ClassName(providerClassName));
258+
providers.add(provider);
259+
} catch (ClassNotFoundException e) {
260+
throw new ConfigurationException("Failed to load " + SASL_AUTHENTICATION_SERVICE_FACTORY_PROVIDER_EXTENSIONS + " class " + providerClassName, e);
261+
}
262+
}
263+
return providers.build();
264+
}
265+
230266
ImmutableList<String> retrieveSaslMechanismClassNames(HierarchicalConfiguration<ImmutableNode> configuration,
231267
DefaultImapSaslMechanismClassNamesProvider defaultImapSaslMechanismClassNamesProvider) throws ConfigurationException {
232268
if (!configuration.containsKey("auth.saslMechanisms")) {
@@ -251,7 +287,7 @@ private ThrowingFunction<HierarchicalConfiguration<ImmutableNode>, ImapSuite> im
251287
StatusResponseFactory statusResponseFactory) {
252288
return configuration -> {
253289
ImapPackage imapPackage = retrievePackages(guiceLoader, configuration);
254-
SaslMechanismRegistry saslMechanisms = retrieveSaslMechanisms(saslMechanismLoader, saslAuthenticationServiceFactoryProviders,
290+
SaslMechanismRegistry saslMechanisms = retrieveSaslMechanisms(saslMechanismLoader, guiceLoader, saslAuthenticationServiceFactoryProviders,
255291
defaultImapSaslMechanismClassNamesProvider, configuration);
256292
DefaultProcessor processor = provideClassImapProcessors(imapPackage, guiceLoader, saslMechanisms, statusResponseFactory);
257293
ImapEncoder encoder = provideImapEncoder(imapPackage, guiceLoader);

server/container/guice/protocols/imap/src/test/java/org/apache/james/modules/protocols/IMAPServerModuleTest.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,36 @@
3131
import org.apache.james.protocols.api.sasl.SaslAuthenticationServiceFactory;
3232
import org.apache.james.protocols.api.sasl.SaslProtocol;
3333
import org.apache.james.protocols.api.sasl.SaslSessionContext;
34+
import org.apache.james.utils.ClassName;
35+
import org.apache.james.utils.GuiceLoader;
36+
import org.apache.james.utils.NamingScheme;
3437
import org.junit.jupiter.api.Test;
3538

3639
import com.google.common.collect.ImmutableList;
3740
import com.google.common.collect.ImmutableSet;
41+
import com.google.inject.Module;
3842

3943
class IMAPServerModuleTest {
4044
private static final JamesDefaultImapSaslMechanismClassNamesProvider JAMES_DEFAULT_PROVIDER = new JamesDefaultImapSaslMechanismClassNamesProvider();
45+
private static final GuiceLoader GUICE_LOADER = new GuiceLoader() {
46+
@Override
47+
public <T> T instantiate(ClassName className) throws ClassNotFoundException {
48+
if (className.getName().equals(CustomAuthenticationServiceFactoryProvider.class.getName())) {
49+
return (T) new CustomAuthenticationServiceFactoryProvider();
50+
}
51+
throw new ClassNotFoundException(className.getName());
52+
}
53+
54+
@Override
55+
public <T> InvocationPerformer<T> withNamingSheme(NamingScheme namingSheme) {
56+
throw new UnsupportedOperationException();
57+
}
58+
59+
@Override
60+
public <T> InvocationPerformer<T> withChildModule(Module childModule) {
61+
throw new UnsupportedOperationException();
62+
}
63+
};
4164

4265
private record CustomAuthenticationServiceFactoryProvider() implements ImapSaslAuthenticationServiceFactoryProvider {
4366
@Override
@@ -124,12 +147,40 @@ void retrieveSaslMechanismClassNamesShouldRejectBlankEntry() {
124147
}
125148

126149
@Test
127-
void extensionSaslMechanismShouldLoadItsOwnAuthConfiguration() throws Exception {
150+
void extensionSaslMechanismShouldLoadItsOwnAuthConfigurationFromBoundProvider() throws Exception {
128151
BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
129152
configuration.addProperty("auth.custom.realm", "james.example");
130153
ImapSaslAuthenticationServiceFactoryProvider provider = new CustomAuthenticationServiceFactoryProvider();
131154

132-
assertThat(testee.retrieveSaslAuthenticationServiceFactories(configuration, ImmutableSet.of(provider)))
155+
assertThat(testee.retrieveSaslAuthenticationServiceFactories(configuration, GUICE_LOADER, ImmutableSet.of(provider)))
133156
.containsExactly(new CustomAuthenticationServiceFactory("james.example"));
134157
}
158+
159+
@Test
160+
void extensionSaslMechanismShouldLoadItsOwnAuthConfigurationFromConfiguredProvider() throws Exception {
161+
BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
162+
configuration.addProperty("auth.custom.realm", "james.example");
163+
configuration.addProperty("auth.saslAuthenticationServiceFactoryProviderExtensions", CustomAuthenticationServiceFactoryProvider.class.getName());
164+
165+
assertThat(testee.retrieveSaslAuthenticationServiceFactories(configuration, GUICE_LOADER, ImmutableSet.of()))
166+
.containsExactly(new CustomAuthenticationServiceFactory("james.example"));
167+
}
168+
169+
@Test
170+
void retrieveConfiguredSaslAuthenticationServiceFactoryProvidersShouldRejectBlankConfiguredList() {
171+
BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
172+
configuration.addProperty("auth.saslAuthenticationServiceFactoryProviderExtensions", " ");
173+
174+
assertThatThrownBy(() -> testee.retrieveConfiguredSaslAuthenticationServiceFactoryProviders(configuration, GUICE_LOADER))
175+
.isInstanceOf(ConfigurationException.class);
176+
}
177+
178+
@Test
179+
void retrieveConfiguredSaslAuthenticationServiceFactoryProvidersShouldFailWhenClassIsUnknown() {
180+
BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
181+
configuration.addProperty("auth.saslAuthenticationServiceFactoryProviderExtensions", "com.example.MissingProvider");
182+
183+
assertThatThrownBy(() -> testee.retrieveConfiguredSaslAuthenticationServiceFactoryProviders(configuration, GUICE_LOADER))
184+
.isInstanceOf(ConfigurationException.class);
185+
}
135186
}

0 commit comments

Comments
 (0)