| /**************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one * |
| * or more contributor license agreements. See the NOTICE file * |
| * distributed with this work for additional information * |
| * regarding copyright ownership. The ASF licenses this file * |
| * to you under the Apache License, Version 2.0 (the * |
| * "License"); you may not use this file except in compliance * |
| * with the License. You may obtain a copy of the License at * |
| * * |
| * http://www.apache.org/licenses/LICENSE-2.0 * |
| * * |
| * Unless required by applicable law or agreed to in writing, * |
| * software distributed under the License is distributed on an * |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * |
| * KIND, either express or implied. See the License for the * |
| * specific language governing permissions and limitations * |
| * under the License. * |
| ****************************************************************/ |
| |
| package org.apache.james.modules.server; |
| |
| import static org.apache.james.webadmin.WebAdminConfiguration.DISABLED_CONFIGURATION; |
| |
| import java.io.FileNotFoundException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import javax.inject.Named; |
| |
| import org.apache.commons.configuration2.Configuration; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.james.filesystem.api.FileSystem; |
| import org.apache.james.jwt.JwtConfiguration; |
| import org.apache.james.jwt.JwtTokenVerifier; |
| import org.apache.james.server.task.json.TaskExtensionModule; |
| import org.apache.james.server.task.json.dto.AdditionalInformationDTO; |
| import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule; |
| import org.apache.james.task.TaskExecutionDetails; |
| import org.apache.james.utils.ClassName; |
| import org.apache.james.utils.ExtensionConfiguration; |
| import org.apache.james.utils.GuiceGenericLoader; |
| import org.apache.james.utils.GuiceProbe; |
| import org.apache.james.utils.InitializationOperation; |
| import org.apache.james.utils.InitilizationOperationBuilder; |
| import org.apache.james.utils.NamingScheme; |
| import org.apache.james.utils.PropertiesProvider; |
| import org.apache.james.utils.WebAdminGuiceProbe; |
| import org.apache.james.webadmin.FixedPortSupplier; |
| import org.apache.james.webadmin.PortSupplier; |
| import org.apache.james.webadmin.RandomPortSupplier; |
| import org.apache.james.webadmin.Routes; |
| import org.apache.james.webadmin.TlsConfiguration; |
| import org.apache.james.webadmin.WebAdminConfiguration; |
| import org.apache.james.webadmin.WebAdminServer; |
| import org.apache.james.webadmin.authentication.AuthenticationFilter; |
| import org.apache.james.webadmin.authentication.JwtFilter; |
| import org.apache.james.webadmin.authentication.NoAuthenticationFilter; |
| import org.apache.james.webadmin.dto.DTOModuleInjections; |
| import org.apache.james.webadmin.mdc.RequestLogger; |
| import org.apache.james.webadmin.utils.JsonTransformer; |
| import org.apache.james.webadmin.utils.JsonTransformerModule; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.github.fge.lambdas.Throwing; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableList; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Provider; |
| import com.google.inject.Provides; |
| import com.google.inject.Scopes; |
| import com.google.inject.Singleton; |
| import com.google.inject.multibindings.Multibinder; |
| import com.google.inject.multibindings.ProvidesIntoSet; |
| |
| public class WebAdminServerModule extends AbstractModule { |
| private static final Logger LOGGER = LoggerFactory.getLogger(WebAdminServerModule.class); |
| |
| private static final boolean DEFAULT_JWT_DISABLED = false; |
| private static final boolean DEFAULT_DISABLED = false; |
| private static final String DEFAULT_NO_CORS_ORIGIN = null; |
| private static final boolean DEFAULT_CORS_DISABLED = false; |
| private static final String DEFAULT_NO_KEYSTORE = null; |
| private static final boolean DEFAULT_HTTPS_DISABLED = false; |
| private static final String DEFAULT_NO_PASSWORD = null; |
| private static final String DEFAULT_NO_TRUST_KEYSTORE = null; |
| private static final String DEFAULT_NO_TRUST_PASSWORD = null; |
| |
| @Override |
| protected void configure() { |
| install(new TaskRoutesModule()); |
| install(new HealthCheckRoutesModule()); |
| install(new ServerRouteModule()); |
| |
| bind(JsonTransformer.class).in(Scopes.SINGLETON); |
| bind(WebAdminServer.class).in(Scopes.SINGLETON); |
| |
| Multibinder.newSetBinder(binder(), GuiceProbe.class).addBinding().to(WebAdminGuiceProbe.class); |
| Multibinder.newSetBinder(binder(), JsonTransformerModule.class); |
| |
| Multibinder.newSetBinder(binder(), RequestLogger.class); |
| } |
| |
| @Provides |
| @Singleton |
| @Named("webAdminRoutes") |
| public List<Routes> provideRoutes(GuiceGenericLoader loader, WebAdminConfiguration configuration, Set<Routes> routesList) { |
| List<Routes> customRoutes = configuration.getAdditionalRoutes() |
| .stream() |
| .map(ClassName::new) |
| .map(Throwing.function(loader.<Routes>withNamingSheme(NamingScheme.IDENTITY)::instantiate)) |
| .peek(routes -> LOGGER.info("Loading WebAdmin route extension {}", routes.getClass().getCanonicalName())) |
| .collect(ImmutableList.toImmutableList()); |
| |
| return ImmutableList.<Routes>builder() |
| .addAll(routesList) |
| .addAll(customRoutes) |
| .build(); |
| } |
| |
| @Provides |
| @Singleton |
| @Named(DTOModuleInjections.CUSTOM_WEBADMIN_DTO) |
| public Set<AdditionalInformationDTOModule<? extends TaskExecutionDetails.AdditionalInformation, ? extends AdditionalInformationDTO>> provideAdditionalDTOs(GuiceGenericLoader loader, ExtensionConfiguration extensionConfiguration) { |
| return extensionConfiguration.getTaskExtensions() |
| .stream() |
| .map(Throwing.function(loader.<TaskExtensionModule>withNamingSheme(NamingScheme.IDENTITY)::instantiate)) |
| .map(TaskExtensionModule::taskAdditionalInformationDTOModules) |
| .flatMap(Collection::stream) |
| .collect(Collectors.toSet()); |
| } |
| |
| @Provides |
| @Singleton |
| public WebAdminConfiguration provideWebAdminConfiguration(FileSystem fileSystem, PropertiesProvider propertiesProvider) throws Exception { |
| try { |
| Configuration configurationFile = propertiesProvider.getConfiguration("webadmin"); |
| |
| List<String> additionalRoutes = additionalRoutes(configurationFile); |
| |
| return WebAdminConfiguration.builder() |
| .enable(configurationFile.getBoolean("enabled", DEFAULT_DISABLED)) |
| .port(port(configurationFile)) |
| .tls(readHttpsConfiguration(configurationFile)) |
| .enableCORS(configurationFile.getBoolean("cors.enable", DEFAULT_CORS_DISABLED)) |
| .urlCORSOrigin(configurationFile.getString("cors.origin", DEFAULT_NO_CORS_ORIGIN)) |
| .host(configurationFile.getString("host", WebAdminConfiguration.DEFAULT_HOST)) |
| .additionalRoutes(additionalRoutes) |
| .jwtPublicKeyPEM(loadPublicKey(fileSystem, |
| Optional.ofNullable(configurationFile.getString("jwt.publickeypem.url", null)))) |
| .maxThreadCount(Optional.ofNullable(configurationFile.getInteger("maxThreadCount", null))) |
| .minThreadCount(Optional.ofNullable(configurationFile.getInteger("minThreadCount", null))) |
| .build(); |
| } catch (FileNotFoundException e) { |
| LOGGER.info("No webadmin.properties file. Disabling WebAdmin interface."); |
| return DISABLED_CONFIGURATION; |
| } |
| } |
| |
| private PortSupplier port(Configuration configurationFile) { |
| int portNumber = configurationFile.getInt("port", WebAdminServer.DEFAULT_PORT); |
| |
| if (portNumber == 0) { |
| return new RandomPortSupplier(); |
| } |
| return new FixedPortSupplier(portNumber); |
| } |
| |
| @VisibleForTesting |
| ImmutableList<String> additionalRoutes(Configuration configurationFile) { |
| return ImmutableList.copyOf(configurationFile.getStringArray("extensions.routes")); |
| } |
| |
| private Optional<String> loadPublicKey(FileSystem fileSystem, Optional<String> jwtPublickeyPemUrl) { |
| return jwtPublickeyPemUrl.map(Throwing.function(url -> FileUtils.readFileToString(fileSystem.getFile(url), StandardCharsets.US_ASCII))); |
| } |
| |
| @Provides |
| @Singleton |
| public AuthenticationFilter providesAuthenticationFilter(PropertiesProvider propertiesProvider, |
| @Named("webadmin") JwtTokenVerifier.Factory jwtTokenVerifier) throws Exception { |
| try { |
| Configuration configurationFile = propertiesProvider.getConfiguration("webadmin"); |
| if (configurationFile.getBoolean("jwt.enabled", DEFAULT_JWT_DISABLED)) { |
| return new JwtFilter(jwtTokenVerifier); |
| } |
| return new NoAuthenticationFilter(); |
| } catch (FileNotFoundException e) { |
| return new NoAuthenticationFilter(); |
| } |
| } |
| |
| @Provides |
| @Singleton |
| @Named("webadmin") |
| JwtTokenVerifier.Factory providesJwtTokenVerifier(WebAdminConfiguration webAdminConfiguration, |
| @Named("jmap") Provider<JwtTokenVerifier> jmapTokenVerifier) { |
| return () -> webAdminConfiguration.getJwtPublicKey() |
| .map(keyPath -> new JwtConfiguration(ImmutableList.of(keyPath))) |
| .map(JwtTokenVerifier::create) |
| .orElseGet(jmapTokenVerifier::get); |
| } |
| |
| private Optional<TlsConfiguration> readHttpsConfiguration(Configuration configurationFile) { |
| boolean enabled = configurationFile.getBoolean("https.enabled", DEFAULT_HTTPS_DISABLED); |
| if (enabled) { |
| return Optional.of(TlsConfiguration.builder() |
| .raw(configurationFile.getString("https.keystore", DEFAULT_NO_KEYSTORE), |
| configurationFile.getString("https.password", DEFAULT_NO_PASSWORD), |
| configurationFile.getString("https.trust.keystore", DEFAULT_NO_TRUST_KEYSTORE), |
| configurationFile.getString("https.trust.password", DEFAULT_NO_TRUST_PASSWORD)) |
| .build()); |
| } |
| return Optional.empty(); |
| } |
| |
| @ProvidesIntoSet |
| InitializationOperation workQueue(WebAdminServer instance) { |
| return InitilizationOperationBuilder |
| .forClass(WebAdminServer.class) |
| .init(instance::start); |
| } |
| } |