1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.console;
20
21 import com.giffing.wicket.spring.boot.starter.app.WicketBootSecuredWebApplication;
22 import de.agilecoders.wicket.core.Bootstrap;
23 import de.agilecoders.wicket.core.settings.BootstrapSettings;
24 import de.agilecoders.wicket.core.settings.IBootstrapSettings;
25 import de.agilecoders.wicket.core.settings.SingleThemeProvider;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.stream.Collectors;
29 import org.apache.syncope.client.console.annotations.UserFormFinalize;
30 import org.apache.syncope.client.console.commons.AccessPolicyConfProvider;
31 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
32 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionsProvider;
33 import org.apache.syncope.client.console.commons.AnyWizardBuilderAdditionalSteps;
34 import org.apache.syncope.client.console.commons.ExternalResourceProvider;
35 import org.apache.syncope.client.console.commons.ImplementationInfoProvider;
36 import org.apache.syncope.client.console.commons.PolicyTabProvider;
37 import org.apache.syncope.client.console.commons.RealmsUtils;
38 import org.apache.syncope.client.console.commons.StatusProvider;
39 import org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
40 import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
41 import org.apache.syncope.client.console.pages.BasePage;
42 import org.apache.syncope.client.console.pages.Dashboard;
43 import org.apache.syncope.client.console.pages.Login;
44 import org.apache.syncope.client.console.pages.MustChangePassword;
45 import org.apache.syncope.client.console.rest.RealmRestClient;
46 import org.apache.syncope.client.console.wizards.any.UserFormFinalizer;
47 import org.apache.syncope.client.lib.SyncopeAnonymousClient;
48 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
49 import org.apache.syncope.client.ui.commons.Constants;
50 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
51 import org.apache.syncope.client.ui.commons.annotations.Resource;
52 import org.apache.syncope.client.ui.commons.themes.AdminLTE;
53 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
54 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
55 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
56 import org.apache.syncope.common.rest.api.beans.RealmQuery;
57 import org.apache.wicket.Page;
58 import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
59 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
60 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
61 import org.apache.wicket.markup.html.WebPage;
62 import org.apache.wicket.protocol.http.WebApplication;
63 import org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory;
64 import org.apache.wicket.protocol.ws.WebSocketAwareResourceIsolationRequestCycleListener;
65 import org.apache.wicket.protocol.ws.api.WebSocketResponse;
66 import org.apache.wicket.request.component.IRequestablePage;
67 import org.apache.wicket.request.cycle.IRequestCycleListener;
68 import org.apache.wicket.request.cycle.RequestCycle;
69 import org.apache.wicket.request.http.WebResponse;
70 import org.apache.wicket.request.mapper.parameter.PageParameters;
71 import org.apache.wicket.request.resource.IResource;
72 import org.apache.wicket.request.resource.ResourceReference;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75 import org.springframework.aop.support.AopUtils;
76 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
77
78 public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
79
80 protected static final Logger LOG = LoggerFactory.getLogger(SyncopeWebApplication.class);
81
82 public static SyncopeWebApplication get() {
83 return (SyncopeWebApplication) WebApplication.get();
84 }
85
86 protected final ConsoleProperties props;
87
88 protected final ClassPathScanImplementationLookup lookup;
89
90 protected final ServiceOps serviceOps;
91
92 protected final ExternalResourceProvider resourceProvider;
93
94 protected final AnyDirectoryPanelAdditionalActionsProvider anyDirectoryPanelAdditionalActionsProvider;
95
96 protected final AnyDirectoryPanelAdditionalActionLinksProvider anyDirectoryPanelAdditionalActionLinksProvider;
97
98 protected final AnyWizardBuilderAdditionalSteps anyWizardBuilderAdditionalSteps;
99
100 protected final StatusProvider statusProvider;
101
102 protected final VirSchemaDetailsPanelProvider virSchemaDetailsPanelProvider;
103
104 protected final ImplementationInfoProvider implementationInfoProvider;
105
106 protected final AccessPolicyConfProvider accessPolicyConfProvider;
107
108 protected final List<PolicyTabProvider> policyTabProviders;
109
110 protected final List<UserFormFinalizer> userFormFinalizers;
111
112 protected final List<IResource> resources;
113
114 public SyncopeWebApplication(
115 final ConsoleProperties props,
116 final ClassPathScanImplementationLookup lookup,
117 final ServiceOps serviceOps,
118 final ExternalResourceProvider resourceProvider,
119 final AnyDirectoryPanelAdditionalActionsProvider anyDirectoryPanelAdditionalActionsProvider,
120 final AnyDirectoryPanelAdditionalActionLinksProvider anyDirectoryPanelAdditionalActionLinksProvider,
121 final AnyWizardBuilderAdditionalSteps anyWizardBuilderAdditionalSteps,
122 final StatusProvider statusProvider,
123 final VirSchemaDetailsPanelProvider virSchemaDetailsPanelProvider,
124 final ImplementationInfoProvider implementationInfoProvider,
125 final AccessPolicyConfProvider accessPolicyConfProvider,
126 final List<PolicyTabProvider> policyTabProviders,
127 final List<UserFormFinalizer> userFormFinalizers,
128 final List<IResource> resources) {
129
130 this.props = props;
131 this.lookup = lookup;
132 this.serviceOps = serviceOps;
133 this.resourceProvider = resourceProvider;
134 this.anyDirectoryPanelAdditionalActionsProvider = anyDirectoryPanelAdditionalActionsProvider;
135 this.anyDirectoryPanelAdditionalActionLinksProvider = anyDirectoryPanelAdditionalActionLinksProvider;
136 this.anyWizardBuilderAdditionalSteps = anyWizardBuilderAdditionalSteps;
137 this.statusProvider = statusProvider;
138 this.virSchemaDetailsPanelProvider = virSchemaDetailsPanelProvider;
139 this.implementationInfoProvider = implementationInfoProvider;
140 this.accessPolicyConfProvider = accessPolicyConfProvider;
141 this.policyTabProviders = policyTabProviders;
142 this.userFormFinalizers = userFormFinalizers;
143 this.resources = resources;
144 }
145
146 protected SyncopeUIRequestCycleListener buildSyncopeUIRequestCycleListener() {
147 return new SyncopeUIRequestCycleListener() {
148
149 @Override
150 protected boolean isSignedIn() {
151 return SyncopeConsoleSession.get().isSignedIn();
152 }
153
154 @Override
155 protected void invalidateSession() {
156 SyncopeConsoleSession.get().invalidate();
157 }
158
159 @Override
160 protected IRequestablePage getErrorPage(final PageParameters errorParameters) {
161 return new Login(errorParameters);
162 }
163 };
164 }
165
166 protected void initSecurity() {
167 if (props.isxForward()) {
168 XForwardedRequestWrapperFactory.Config config = new XForwardedRequestWrapperFactory.Config();
169 config.setProtocolHeader(props.getxForwardProtocolHeader());
170 config.setHttpServerPort(props.getxForwardHttpPort());
171 config.setHttpsServerPort(props.getxForwardHttpsPort());
172
173 XForwardedRequestWrapperFactory factory = new XForwardedRequestWrapperFactory();
174 factory.setConfig(config);
175 getFilterFactoryManager().add(factory);
176 }
177
178 if (props.isCsrf()) {
179 getRequestCycleListeners().add(new WebSocketAwareResourceIsolationRequestCycleListener());
180 }
181
182 getCspSettings().blocking().unsafeInline();
183
184 getRequestCycleListeners().add(new IRequestCycleListener() {
185
186 @Override
187 public void onEndRequest(final RequestCycle cycle) {
188 if (cycle.getResponse() instanceof WebResponse && !(cycle.getResponse() instanceof WebSocketResponse)) {
189 props.getSecurityHeaders().
190 forEach((name, value) -> ((WebResponse) cycle.getResponse()).setHeader(name, value));
191 }
192 }
193 });
194 }
195
196 @Override
197 protected void init() {
198 super.init();
199
200
201 IBootstrapSettings settings = new BootstrapSettings();
202
203
204 settings.setThemeProvider(new SingleThemeProvider(new AdminLTE()));
205
206
207 Bootstrap.install(this, settings);
208
209 getResourceSettings().setUseMinifiedResources(true);
210 getResourceSettings().setUseDefaultOnMissingResource(true);
211 getResourceSettings().setThrowExceptionOnMissingResource(false);
212
213 getSecuritySettings().setAuthorizationStrategy(new MetaDataRoleAuthorizationStrategy(this));
214
215 lookup.getIdRepoPageClasses().
216 forEach(cls -> MetaDataRoleAuthorizationStrategy.authorize(cls, Constants.ROLE_AUTHENTICATED));
217
218 getMarkupSettings().setStripWicketTags(true);
219 getMarkupSettings().setCompressWhitespace(true);
220
221 getRequestCycleListeners().add(buildSyncopeUIRequestCycleListener());
222
223 initSecurity();
224
225 mountPage("/login", getSignInPageClass());
226
227 for (IResource resource : resources) {
228 Class<?> resourceClass = AopUtils.getTargetClass(resource);
229 Resource annotation = resourceClass.getAnnotation(Resource.class);
230 if (annotation == null) {
231 LOG.error("No @Resource annotation found, ignoring {}", resourceClass.getName());
232 } else {
233 LOG.debug("Mounting {} under {}", resourceClass.getName(), annotation.path());
234
235 mountResource(annotation.path(), new ResourceReference(annotation.key()) {
236
237 private static final long serialVersionUID = -128426276529456602L;
238
239 @Override
240 public IResource getResource() {
241 return resource;
242 }
243 });
244 }
245 }
246
247
248 if (getDebugSettings().isAjaxDebugModeEnabled()) {
249 getDebugSettings().setComponentPathAttributeName("syncope-path");
250 }
251 }
252
253 @Override
254 protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
255 return SyncopeConsoleSession.class;
256 }
257
258 @Override
259 protected Class<? extends WebPage> getSignInPageClass() {
260 return Login.class;
261 }
262
263 @Override
264 public Class<? extends Page> getHomePage() {
265 return AuthenticatedWebSession.get().isSignedIn()
266 && SyncopeConsoleSession.get().getSelfTO().isMustChangePassword()
267 ? MustChangePassword.class
268 : Dashboard.class;
269 }
270
271 public ClassPathScanImplementationLookup getLookup() {
272 return lookup;
273 }
274
275 public Class<? extends BasePage> getPageClass(final String name) {
276 return props.getPage().get(name);
277 }
278
279 public ThreadPoolTaskExecutor newThreadPoolTaskExecutor() {
280 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
281 executor.setWaitForTasksToCompleteOnShutdown(false);
282 executor.setCorePoolSize(props.getTopology().getCorePoolSize());
283 executor.setMaxPoolSize(props.getTopology().getMaxPoolSize());
284 executor.setQueueCapacity(props.getTopology().getQueueCapacity());
285 executor.initialize();
286 return executor;
287 }
288
289 public SyncopeAnonymousClient newAnonymousClient(final String domain) {
290 return newClientFactory().
291 setDomain(domain).
292 createAnonymous(props.getAnonymousUser(), props.getAnonymousKey());
293 }
294
295 public SyncopeClientFactoryBean newClientFactory() {
296 return new SyncopeClientFactoryBean().
297 setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
298 setUseCompression(props.isUseGZIPCompression());
299 }
300
301 public String getDefaultAnyPanelClass() {
302 return props.getDefaultAnyPanelClass();
303 }
304
305 public String getAdminUser() {
306 return props.getAdminUser();
307 }
308
309 public String getAnonymousUser() {
310 return props.getAnonymousUser();
311 }
312
313 public String getAnonymousKey() {
314 return props.getAnonymousKey();
315 }
316
317 public long getMaxWaitTimeInSeconds() {
318 return props.getMaxWaitTimeOnApplyChanges();
319 }
320
321 public int getMaxUploadFileSizeMB() {
322 return props.getMaxUploadFileSizeMB();
323 }
324
325 public boolean fullRealmsTree(final RealmRestClient restClient) {
326 if (props.getRealmsFullTreeThreshold() <= 0) {
327 return false;
328 }
329
330 RealmQuery query = RealmsUtils.buildRootQuery();
331 query.setPage(1);
332 query.setSize(0);
333 return restClient.search(query).getTotalCount() < props.getRealmsFullTreeThreshold();
334 }
335
336 public ExternalResourceProvider getResourceProvider() {
337 return resourceProvider;
338 }
339
340 public AnyDirectoryPanelAdditionalActionsProvider getAnyDirectoryPanelAdditionalActionsProvider() {
341 return anyDirectoryPanelAdditionalActionsProvider;
342 }
343
344 public AnyDirectoryPanelAdditionalActionLinksProvider getAnyDirectoryPanelAdditionalActionLinksProvider() {
345 return anyDirectoryPanelAdditionalActionLinksProvider;
346 }
347
348 public AnyWizardBuilderAdditionalSteps getAnyWizardBuilderAdditionalSteps() {
349 return anyWizardBuilderAdditionalSteps;
350 }
351
352 public StatusProvider getStatusProvider() {
353 return statusProvider;
354 }
355
356 public VirSchemaDetailsPanelProvider getVirSchemaDetailsPanelProvider() {
357 return virSchemaDetailsPanelProvider;
358 }
359
360 public ImplementationInfoProvider getImplementationInfoProvider() {
361 return implementationInfoProvider;
362 }
363
364 public Collection<PolicyTabProvider> getPolicyTabProviders() {
365 return policyTabProviders;
366 }
367
368 public List<UserFormFinalizer> getFormFinalizers(final AjaxWizard.Mode mode) {
369 return userFormFinalizers.stream().filter(uff -> {
370 Class<?> clazz = AopUtils.getTargetClass(uff);
371 UserFormFinalize annotation = clazz.getAnnotation(UserFormFinalize.class);
372 if (annotation == null) {
373 LOG.error("No @UserFormFinalize annotation found, ignoring {}", clazz.getName());
374 return false;
375 }
376
377 return annotation.mode() == mode;
378 }).collect(Collectors.toList());
379 }
380
381 public AccessPolicyConfProvider getAccessPolicyConfProvider() {
382 return accessPolicyConfProvider;
383 }
384 }