/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed 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 com.evolveum.midpoint.web.security;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import com.evolveum.midpoint.model.common.SystemObjectCache;
import com.evolveum.midpoint.model.common.expression.ExpressionFactory;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.web.page.error.*;
import com.evolveum.midpoint.wf.api.WorkflowManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.io.IOUtils;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
import org.apache.wicket.core.request.mapper.MountedMapper;
import org.apache.wicket.markup.head.PriorityFirstComparator;
import org.apache.wicket.markup.html.SecurePackageResourceGuard;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.SharedResourceReference;
import org.apache.wicket.resource.loader.IStringResourceLoader;
import org.apache.wicket.settings.ApplicationSettings;
import org.apache.wicket.settings.ResourceSettings;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.apache.wicket.util.lang.Bytes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration;
import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.model.api.ModelAuditService;
import com.evolveum.midpoint.model.api.ModelInteractionService;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.TaskService;
import com.evolveum.midpoint.model.api.WorkflowService;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.DescriptorLoader;
import com.evolveum.midpoint.web.component.GuiComponents;
import com.evolveum.midpoint.web.page.admin.home.PageDashboard;
import com.evolveum.midpoint.web.page.login.PageLogin;
import com.evolveum.midpoint.web.page.self.PageSelfDashboard;
import com.evolveum.midpoint.web.resource.img.ImgResources;
import com.evolveum.midpoint.web.util.Utf8BundleStringResourceLoader;
/**
* @author lazyman
*/
@Component("midpointApplication")
public class MidPointApplication extends AuthenticatedWebApplication {
/**
* Max. photo size for user/jpegPhoto
*/
public static final Bytes FOCUS_PHOTO_MAX_FILE_SIZE = Bytes.kilobytes(192);
public static final String WEB_APP_CONFIGURATION = "midpoint.webApplication";
public static final List<LocaleDescriptor> AVAILABLE_LOCALES;
private static final String LOCALIZATION_DESCRIPTOR = "/localization/locale.properties";
private static final String PROP_NAME = ".name";
private static final String PROP_FLAG = ".flag";
private static final String PROP_DEFAULT = ".default";
private static final Trace LOGGER = TraceManager.getTrace(MidPointApplication.class);
static {
List<LocaleDescriptor> locales = new ArrayList<>();
try {
ClassLoader classLoader = MidPointApplication.class.getClassLoader();
Enumeration<URL> urls = classLoader.getResources(LOCALIZATION_DESCRIPTOR);
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
LOGGER.debug("Found localization descriptor {}.", new Object[]{url.toString()});
Properties properties = new Properties();
Reader reader = null;
try {
reader = new InputStreamReader(url.openStream(), "utf-8");
properties.load(reader);
Map<String, Map<String, String>> localeMap = new HashMap<>();
Set<String> keys = (Set) properties.keySet();
for (String key : keys) {
String[] array = key.split("\\.");
if (array.length != 2) {
continue;
}
String locale = array[0];
Map<String, String> map = localeMap.get(locale);
if (map == null) {
map = new HashMap<>();
localeMap.put(locale, map);
}
map.put(key, properties.getProperty(key));
}
for (String key : localeMap.keySet()) {
Map<String, String> localeDefinition = localeMap.get(key);
if (!localeDefinition.containsKey(key + PROP_NAME)
|| !localeDefinition.containsKey(key + PROP_FLAG)) {
continue;
}
LocaleDescriptor descriptor = new LocaleDescriptor(
localeDefinition.get(key + PROP_NAME),
localeDefinition.get(key + PROP_FLAG),
localeDefinition.get(key + PROP_DEFAULT),
WebComponentUtil.getLocaleFromString(key)
);
locales.add(descriptor);
}
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load localization", ex);
} finally {
IOUtils.closeQuietly(reader);
}
}
Collections.sort(locales);
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load locales", ex);
}
AVAILABLE_LOCALES = Collections.unmodifiableList(locales);
}
@Autowired
transient ModelService model;
@Autowired
transient ModelInteractionService modelInteractionService;
@Autowired
transient TaskService taskService;
@Autowired
transient PrismContext prismContext;
@Autowired
transient ExpressionFactory expressionFactory;
@Autowired
transient TaskManager taskManager;
@Autowired
transient ModelAuditService auditService;
@Autowired
transient private RepositoryService repositoryService; // temporary
@Autowired
transient private WorkflowService workflowService;
@Autowired
transient private WorkflowManager workflowManager;
@Autowired
transient MidpointConfiguration configuration;
@Autowired
transient Protector protector;
@Autowired
transient MatchingRuleRegistry matchingRuleRegistry;
@Autowired
transient SecurityEnforcer securityEnforcer;
@Autowired
transient SystemObjectCache systemObjectCache;
private WebApplicationConfiguration webApplicationConfiguration;
@Override
protected void onDestroy() {
GuiComponents.destroy();
super.onDestroy();
}
@Override
public Class<? extends PageBase> getHomePage() {
if (WebComponentUtil.isAuthorized(AuthorizationConstants.AUTZ_UI_DASHBOARD_URL,
AuthorizationConstants.AUTZ_UI_HOME_ALL_URL)) {
return PageDashboard.class;
} else {
return PageSelfDashboard.class;
}
}
@Override
public void init() {
super.init();
getJavaScriptLibrarySettings().setJQueryReference(
new PackageResourceReference(MidPointApplication.class,
"../../../../../webjars/adminlte/2.3.11/plugins/jQuery/jquery-2.2.3.min.js"));
GuiComponents.init();
getComponentInstantiationListeners().add(new SpringComponentInjector(this));
ResourceSettings resourceSettings = getResourceSettings();
resourceSettings.setParentFolderPlaceholder("$-$");
resourceSettings.setHeaderItemComparator(new PriorityFirstComparator(true));
SecurePackageResourceGuard guard = (SecurePackageResourceGuard) resourceSettings.getPackageResourceGuard();
guard.addPattern("+*.woff2");
List<IStringResourceLoader> resourceLoaders = resourceSettings.getStringResourceLoaders();
resourceLoaders.add(0, new Utf8BundleStringResourceLoader("localization/Midpoint"));
resourceLoaders.add(1, new Utf8BundleStringResourceLoader(SchemaConstants.SCHEMA_LOCALIZATION_PROPERTIES_RESOURCE_BASE_PATH));
resourceSettings.setThrowExceptionOnMissingResource(false);
getMarkupSettings().setStripWicketTags(true);
// getMarkupSettings().setDefaultBeforeDisabledLink("");
// getMarkupSettings().setDefaultAfterDisabledLink("");
if (RuntimeConfigurationType.DEVELOPMENT.equals(getConfigurationType())) {
getDebugSettings().setAjaxDebugModeEnabled(true);
getDebugSettings().setDevelopmentUtilitiesEnabled(true);
}
//pretty url for resources (e.g. images)
mountFiles(ImgResources.BASE_PATH, ImgResources.class);
//exception handling an error pages
ApplicationSettings appSettings = getApplicationSettings();
appSettings.setAccessDeniedPage(PageError401.class);
appSettings.setInternalErrorPage(PageError.class);
appSettings.setPageExpiredErrorPage(PageError.class);
mount(new MountedMapper("/error", PageError.class, new PageParametersEncoder()));
mount(new MountedMapper("/error/401", PageError401.class, new PageParametersEncoder()));
mount(new MountedMapper("/error/403", PageError403.class, new PageParametersEncoder()));
mount(new MountedMapper("/error/404", PageError404.class, new PageParametersEncoder()));
mount(new MountedMapper("/error/410", PageError410.class, new PageParametersEncoder()));
getRequestCycleListeners().add(new LoggingRequestCycleListener(this));
//descriptor loader, used for customization
new DescriptorLoader().loadData(this);
}
private void mountFiles(String path, Class<?> clazz) {
try {
List<Resource> list = new ArrayList<>();
String packagePath = clazz.getPackage().getName().replace('.', '/');
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] res = resolver.getResources("classpath:" + packagePath + "/*.png");
if (res != null) {
list.addAll(Arrays.asList(res));
}
res = resolver.getResources("classpath:" + packagePath + "/*.gif");
if (res != null) {
list.addAll(Arrays.asList(res));
}
for (Resource resource : list) {
URI uri = resource.getURI();
File file = new File(uri.toString());
mountResource(path + "/" + file.getName(), new SharedResourceReference(clazz, file.getName()));
}
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't mount files", ex);
}
}
public WebApplicationConfiguration getWebApplicationConfiguration() {
if (webApplicationConfiguration == null) {
Configuration config = configuration.getConfiguration(WEB_APP_CONFIGURATION);
webApplicationConfiguration = new WebApplicationConfiguration(config);
}
return webApplicationConfiguration;
}
public SecurityEnforcer getSecurityEnforcer() {
return securityEnforcer;
}
public ModelService getModel() {
return model;
}
public TaskManager getTaskManager() {
return taskManager;
}
public ModelAuditService getAuditService() {
return auditService;
}
public RepositoryService getRepositoryService() {
return repositoryService;
}
public TaskService getTaskService() {
return taskService;
}
public PrismContext getPrismContext() {
return prismContext;
}
public ExpressionFactory getExpressionFactory() {
return expressionFactory;
}
public Protector getProtector() {
return protector;
}
@Override
protected Class<? extends WebPage> getSignInPageClass() {
return PageLogin.class;
}
@Override
protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
return MidPointAuthWebSession.class;
}
public WorkflowService getWorkflowService() {
return workflowService;
}
public WorkflowManager getWorkflowManager() {
return workflowManager;
}
public ModelInteractionService getModelInteractionService() {
return modelInteractionService;
}
public static boolean containsLocale(Locale locale) {
if (locale == null) {
return false;
}
for (LocaleDescriptor descriptor : AVAILABLE_LOCALES) {
if (locale.equals(descriptor.getLocale())) {
return true;
}
}
return false;
}
public static Locale getDefaultLocale() {
for (LocaleDescriptor descriptor : AVAILABLE_LOCALES) {
if (descriptor.isDefault()) {
return descriptor.getLocale();
}
}
return new Locale("en", "US");
}
public MatchingRuleRegistry getMatchingRuleRegistry() {
return matchingRuleRegistry;
}
public SystemConfigurationType getSystemConfiguration() throws SchemaException {
PrismObject<SystemConfigurationType> config = systemObjectCache.getSystemConfiguration(new OperationResult("dummy"));
return config != null ? config.asObjectable() : null;
}
public SystemConfigurationType getSystemConfigurationIfAvailable() {
try {
PrismObject<SystemConfigurationType> config = systemObjectCache.getSystemConfiguration(new OperationResult("dummy"));
return config != null ? config.asObjectable() : null;
} catch (SchemaException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't retrieve system configuration", e);
return null;
}
}
private static class ResourceFileFilter implements FilenameFilter {
@Override
public boolean accept(File parent, String name) {
if (name.endsWith("png") || name.endsWith("gif")) {
return true;
}
return false;
}
}
public static MidPointApplication get() {
return (MidPointApplication) WebApplication.get();
}
}