View Javadoc
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 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         // Application settings
201         IBootstrapSettings settings = new BootstrapSettings();
202 
203         // set theme provider
204         settings.setThemeProvider(new SingleThemeProvider(new AdminLTE()));
205 
206         // install application settings
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         // enable component path
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 }