f03f8233502cefe7892d9f41254c5e9db86b6eb1
[syncope.git] / client / console / src / main / java / org / apache / syncope / client / console / SyncopeConsoleApplication.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.syncope.client.console;
20
21 import de.agilecoders.wicket.core.Bootstrap;
22 import de.agilecoders.wicket.core.settings.BootstrapSettings;
23 import de.agilecoders.wicket.core.settings.IBootstrapSettings;
24 import de.agilecoders.wicket.core.settings.SingleThemeProvider;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.stream.Collectors;
34 import org.apache.commons.collections4.ListUtils;
35 import org.apache.commons.lang3.BooleanUtils;
36 import org.apache.commons.lang3.ClassUtils;
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.syncope.client.console.annotations.Resource;
39 import org.apache.syncope.client.console.commons.Constants;
40 import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
41 import org.apache.syncope.client.console.init.ConsoleInitializer;
42 import org.apache.syncope.client.console.pages.BasePage;
43 import org.apache.syncope.client.console.pages.Dashboard;
44 import org.apache.syncope.client.console.pages.MustChangePassword;
45 import org.apache.syncope.client.console.pages.Login;
46 import org.apache.syncope.client.console.themes.AdminLTE;
47 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
48 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
49 import org.apache.syncope.common.lib.PropertyUtils;
50 import org.apache.syncope.common.lib.SyncopeConstants;
51 import org.apache.syncope.common.lib.to.EntityTO;
52 import org.apache.syncope.common.lib.types.StandardEntitlement;
53 import org.apache.syncope.common.rest.api.service.DomainService;
54 import org.apache.wicket.Page;
55 import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
56 import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
57 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
58 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
59 import org.apache.wicket.markup.html.WebPage;
60 import org.apache.wicket.protocol.http.CsrfPreventionRequestCycleListener;
61 import org.apache.wicket.protocol.http.WebApplication;
62 import org.apache.wicket.request.resource.AbstractResource;
63 import org.apache.wicket.request.resource.IResource;
64 import org.apache.wicket.request.resource.ResourceReference;
65 import org.apache.wicket.resource.JQueryResourceReference;
66 import org.apache.wicket.util.lang.Args;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 public class SyncopeConsoleApplication extends AuthenticatedWebApplication {
71
72 private static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleApplication.class);
73
74 private static final String CONSOLE_PROPERTIES = "console.properties";
75
76 public static final List<Locale> SUPPORTED_LOCALES = Collections.unmodifiableList(Arrays.asList(
77 new Locale[] {
78 Locale.ENGLISH, Locale.ITALIAN, new Locale("pt", "BR"), new Locale("ru"), Locale.JAPANESE
79 }));
80
81 public static SyncopeConsoleApplication get() {
82 return (SyncopeConsoleApplication) WebApplication.get();
83 }
84
85 private String site;
86
87 private String anonymousUser;
88
89 private String anonymousKey;
90
91 private String reconciliationReportKey;
92
93 private String scheme;
94
95 private String host;
96
97 private String port;
98
99 private String rootPath;
100
101 private String useGZIPCompression;
102
103 private Integer maxUploadFileSizeMB;
104
105 private Integer maxWaitTime;
106
107 private Integer corePoolSize;
108
109 private Integer maxPoolSize;
110
111 private Integer queueCapacity;
112
113 private List<String> domains;
114
115 private Map<String, Class<? extends BasePage>> pageClasses;
116
117 @SuppressWarnings("unchecked")
118 protected void populatePageClasses(final Properties props) {
119 Enumeration<String> propNames = (Enumeration<String>) props.propertyNames();
120 while (propNames.hasMoreElements()) {
121 String name = propNames.nextElement();
122 if (name.startsWith("page.")) {
123 try {
124 Class<?> clazz = ClassUtils.getClass(props.getProperty(name));
125 if (BasePage.class.isAssignableFrom(clazz)) {
126 pageClasses.put(
127 StringUtils.substringAfter("page.", name), (Class<? extends BasePage>) clazz);
128 } else {
129 LOG.warn("{} does not extend {}, ignoring...", clazz.getName(), BasePage.class.getName());
130 }
131 } catch (ClassNotFoundException e) {
132 LOG.error("While looking for class identified by property '{}'", name, e);
133 }
134 }
135 }
136 }
137
138 @Override
139 protected void init() {
140 super.init();
141
142 // read console.properties
143 Properties props = PropertyUtils.read(getClass(), CONSOLE_PROPERTIES, "console.directory").getLeft();
144
145 site = props.getProperty("site");
146 Args.notNull(site, "<site>");
147 anonymousUser = props.getProperty("anonymousUser");
148 Args.notNull(anonymousUser, "<anonymousUser>");
149 anonymousKey = props.getProperty("anonymousKey");
150 Args.notNull(anonymousKey, "<anonymousKey>");
151
152 scheme = props.getProperty("scheme");
153 Args.notNull(scheme, "<scheme>");
154 host = props.getProperty("host");
155 Args.notNull(host, "<host>");
156 port = props.getProperty("port");
157 Args.notNull(port, "<port>");
158 rootPath = props.getProperty("rootPath");
159 Args.notNull(rootPath, "<rootPath>");
160 useGZIPCompression = props.getProperty("useGZIPCompression");
161 Args.notNull(useGZIPCompression, "<useGZIPCompression>");
162 maxUploadFileSizeMB = props.getProperty("maxUploadFileSizeMB") == null
163 ? null
164 : Integer.valueOf(props.getProperty("maxUploadFileSizeMB"));
165
166 maxWaitTime = Integer.valueOf(props.getProperty("maxWaitTimeOnApplyChanges", "30"));
167
168 // Resource connections check thread pool size
169 corePoolSize = Integer.valueOf(props.getProperty("topology.corePoolSize", "5"));
170 maxPoolSize = Integer.valueOf(props.getProperty("topology.maxPoolSize", "10"));
171 queueCapacity = Integer.valueOf(props.getProperty("topology.queueCapacity", "50"));
172
173 String csrf = props.getProperty("csrf");
174
175 // process page properties
176 pageClasses = new HashMap<>();
177 populatePageClasses(props);
178 pageClasses = Collections.unmodifiableMap(pageClasses);
179
180 // Application settings
181 IBootstrapSettings settings = new BootstrapSettings();
182
183 // set theme provider
184 settings.setThemeProvider(new SingleThemeProvider(new AdminLTE()));
185
186 // install application settings
187 Bootstrap.install(this, settings);
188
189 getResourceSettings().setUseMinifiedResources(true);
190
191 getResourceSettings().setThrowExceptionOnMissingResource(true);
192
193 getJavaScriptLibrarySettings().setJQueryReference(JQueryResourceReference.getV2());
194
195 getSecuritySettings().setAuthorizationStrategy(new MetaDataRoleAuthorizationStrategy(this));
196
197 ClassPathScanImplementationLookup lookup = (ClassPathScanImplementationLookup) getServletContext().
198 getAttribute(ConsoleInitializer.CLASSPATH_LOOKUP);
199 lookup.getPageClasses().
200 forEach(cls -> MetaDataRoleAuthorizationStrategy.authorize(cls, Constants.ROLE_AUTHENTICATED));
201
202 getMarkupSettings().setStripWicketTags(true);
203 getMarkupSettings().setCompressWhitespace(true);
204
205 if (BooleanUtils.toBoolean(csrf)) {
206 getRequestCycleListeners().add(new CsrfPreventionRequestCycleListener());
207 }
208 getRequestCycleListeners().add(new SyncopeConsoleRequestCycleListener());
209
210 mountPage("/login", getSignInPageClass());
211
212 try {
213 reconciliationReportKey = props.getProperty("reconciliationReportKey");
214 } catch (NumberFormatException e) {
215 LOG.error("While parsing reconciliationReportKey", e);
216 }
217 Args.notNull(reconciliationReportKey, "<reconciliationReportKey>");
218
219 for (Class<? extends AbstractResource> resource : lookup.getResources()) {
220 Resource annotation = resource.getAnnotation(Resource.class);
221 try {
222 AbstractResource instance = resource.getDeclaredConstructor().newInstance();
223
224 mountResource(annotation.path(), new ResourceReference(annotation.key()) {
225
226 private static final long serialVersionUID = -128426276529456602L;
227
228 @Override
229 public IResource getResource() {
230 return instance;
231 }
232 });
233 } catch (Exception e) {
234 LOG.error("Could not instantiate {}", resource.getName(), e);
235 }
236 }
237
238 // enable component path
239 if (getDebugSettings().isAjaxDebugModeEnabled()) {
240 getDebugSettings().setComponentPathAttributeName("syncope-path");
241 }
242 }
243
244 @Override
245 protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
246 return SyncopeConsoleSession.class;
247 }
248
249 @Override
250 protected Class<? extends WebPage> getSignInPageClass() {
251 return Login.class;
252 }
253
254 @Override
255 public Class<? extends Page> getHomePage() {
256 return AuthenticatedWebSession.get().isSignedIn()
257 && SyncopeConsoleSession.get().owns(StandardEntitlement.MUST_CHANGE_PASSWORD)
258 ? MustChangePassword.class
259 : Dashboard.class;
260 }
261
262 public Class<? extends BasePage> getPageClass(final String key) {
263 return pageClasses.get(key);
264 }
265
266 public String getSite() {
267 return site;
268 }
269
270 public String getAnonymousUser() {
271 return anonymousUser;
272 }
273
274 public String getAnonymousKey() {
275 return anonymousKey;
276 }
277
278 public String getReconciliationReportKey() {
279 return reconciliationReportKey;
280 }
281
282 public Integer getMaxUploadFileSizeMB() {
283 return maxUploadFileSizeMB;
284 }
285
286 public Integer getMaxWaitTimeInSeconds() {
287 return maxWaitTime;
288 }
289
290 public Integer getCorePoolSize() {
291 return corePoolSize;
292 }
293
294 public Integer getMaxPoolSize() {
295 return maxPoolSize;
296 }
297
298 public Integer getQueueCapacity() {
299 return queueCapacity;
300 }
301
302 public SyncopeClientFactoryBean newClientFactory() {
303 return new SyncopeClientFactoryBean().
304 setAddress(scheme + "://" + host + ":" + port + StringUtils.prependIfMissing(rootPath, "/")).
305 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
306 }
307
308 public List<String> getDomains() {
309 synchronized (LOG) {
310 if (domains == null) {
311 domains = newClientFactory().create(
312 new AnonymousAuthenticationHandler(anonymousUser, anonymousKey)).
313 getService(DomainService.class).list().stream().map(EntityTO::getKey).
314 collect(Collectors.toList());
315 domains.add(0, SyncopeConstants.MASTER_DOMAIN);
316 domains = ListUtils.unmodifiableList(domains);
317 }
318 }
319 return domains;
320 }
321 }