1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.enduser;
20
21 import com.fasterxml.jackson.core.type.TypeReference;
22 import com.fasterxml.jackson.databind.json.JsonMapper;
23 import com.giffing.wicket.spring.boot.starter.app.WicketBootSecuredWebApplication;
24 import de.agilecoders.wicket.core.Bootstrap;
25 import de.agilecoders.wicket.core.settings.BootstrapSettings;
26 import de.agilecoders.wicket.core.settings.IBootstrapSettings;
27 import de.agilecoders.wicket.core.settings.SingleThemeProvider;
28 import java.io.InputStream;
29 import java.util.List;
30 import java.util.Map;
31 import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
32 import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
33 import org.apache.syncope.client.enduser.pages.BasePage;
34 import org.apache.syncope.client.enduser.pages.Dashboard;
35 import org.apache.syncope.client.enduser.pages.Login;
36 import org.apache.syncope.client.enduser.pages.MustChangePassword;
37 import org.apache.syncope.client.enduser.pages.SelfConfirmPasswordReset;
38 import org.apache.syncope.client.enduser.panels.Sidebar;
39 import org.apache.syncope.client.lib.SyncopeAnonymousClient;
40 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
41 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
42 import org.apache.syncope.client.ui.commons.annotations.Resource;
43 import org.apache.syncope.client.ui.commons.themes.AdminLTE;
44 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
45 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
46 import org.apache.wicket.Page;
47 import org.apache.wicket.Session;
48 import org.apache.wicket.WicketRuntimeException;
49 import org.apache.wicket.authorization.IAuthorizationStrategy;
50 import org.apache.wicket.authorization.IAuthorizationStrategy.AllowAllAuthorizationStrategy;
51 import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
52 import org.apache.wicket.markup.html.WebPage;
53 import org.apache.wicket.protocol.http.ResourceIsolationRequestCycleListener;
54 import org.apache.wicket.protocol.http.WebApplication;
55 import org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory;
56 import org.apache.wicket.request.Request;
57 import org.apache.wicket.request.Response;
58 import org.apache.wicket.request.component.IRequestableComponent;
59 import org.apache.wicket.request.component.IRequestablePage;
60 import org.apache.wicket.request.cycle.IRequestCycleListener;
61 import org.apache.wicket.request.cycle.RequestCycle;
62 import org.apache.wicket.request.http.WebResponse;
63 import org.apache.wicket.request.mapper.parameter.PageParameters;
64 import org.apache.wicket.request.resource.IResource;
65 import org.apache.wicket.request.resource.ResourceReference;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68 import org.springframework.aop.support.AopUtils;
69 import org.springframework.core.io.ResourceLoader;
70
71 public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
72
73 protected static final Logger LOG = LoggerFactory.getLogger(SyncopeWebApplication.class);
74
75 protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
76
77 public static SyncopeWebApplication get() {
78 return (SyncopeWebApplication) WebApplication.get();
79 }
80
81 protected final ResourceLoader resourceLoader;
82
83 protected final EnduserProperties props;
84
85 protected final ClassPathScanImplementationLookup lookup;
86
87 protected final ServiceOps serviceOps;
88
89 protected final List<IResource> resources;
90
91 protected UserFormLayoutInfo customFormLayout;
92
93 public SyncopeWebApplication(
94 final ResourceLoader resourceLoader,
95 final EnduserProperties props,
96 final ClassPathScanImplementationLookup lookup,
97 final ServiceOps serviceOps,
98 final List<IResource> resources) {
99
100 this.resourceLoader = resourceLoader;
101 this.props = props;
102 this.lookup = lookup;
103 this.serviceOps = serviceOps;
104 this.resources = resources;
105 }
106
107 protected SyncopeUIRequestCycleListener buildSyncopeUIRequestCycleListener() {
108 return new SyncopeUIRequestCycleListener() {
109
110 @Override
111 protected boolean isSignedIn() {
112 return SyncopeEnduserSession.get().isAuthenticated();
113 }
114
115 @Override
116 protected void invalidateSession() {
117 SyncopeEnduserSession.get().invalidate();
118 }
119
120 @Override
121 protected IRequestablePage getErrorPage(final PageParameters errorParameters) {
122 return new Login(errorParameters);
123 }
124 };
125 }
126
127 protected void initSecurity() {
128 if (props.isxForward()) {
129 XForwardedRequestWrapperFactory.Config config = new XForwardedRequestWrapperFactory.Config();
130 config.setProtocolHeader(props.getxForwardProtocolHeader());
131 config.setHttpServerPort(props.getxForwardHttpPort());
132 config.setHttpsServerPort(props.getxForwardHttpsPort());
133
134 XForwardedRequestWrapperFactory factory = new XForwardedRequestWrapperFactory();
135 factory.setConfig(config);
136 getFilterFactoryManager().add(factory);
137 }
138
139 if (props.isCsrf()) {
140 getRequestCycleListeners().add(new ResourceIsolationRequestCycleListener());
141 }
142
143 getCspSettings().blocking().unsafeInline();
144
145 getRequestCycleListeners().add(new IRequestCycleListener() {
146
147 @Override
148 public void onEndRequest(final RequestCycle cycle) {
149 if (cycle.getResponse() instanceof WebResponse) {
150 props.getSecurityHeaders().
151 forEach((name, value) -> ((WebResponse) cycle.getResponse()).setHeader(name, value));
152 }
153 }
154 });
155 }
156
157 @Override
158 protected void init() {
159 super.init();
160
161
162 IBootstrapSettings settings = new BootstrapSettings();
163
164
165 settings.setThemeProvider(new SingleThemeProvider(new AdminLTE()));
166
167
168 Bootstrap.install(this, settings);
169
170 getResourceSettings().setUseMinifiedResources(true);
171 getResourceSettings().setUseDefaultOnMissingResource(true);
172 getResourceSettings().setThrowExceptionOnMissingResource(false);
173
174 getSecuritySettings().setAuthorizationStrategy(getAuthorizationStrategy());
175
176 getMarkupSettings().setStripWicketTags(true);
177 getMarkupSettings().setCompressWhitespace(true);
178
179 getRequestCycleListeners().add(buildSyncopeUIRequestCycleListener());
180
181 initSecurity();
182
183
184 mountPage("/confirmpasswordreset", SelfConfirmPasswordReset.class);
185
186 for (IResource resource : resources) {
187 Class<?> resourceClass = AopUtils.getTargetClass(resource);
188 Resource annotation = resourceClass.getAnnotation(Resource.class);
189 if (annotation == null) {
190 LOG.error("No @Resource annotation found, ignoring {}", resourceClass.getName());
191 } else {
192 LOG.debug("Mounting {} under {}", resourceClass.getName(), annotation.path());
193
194 mountResource(annotation.path(), new ResourceReference(annotation.key()) {
195
196 private static final long serialVersionUID = -128426276529456602L;
197
198 @Override
199 public IResource getResource() {
200 return resource;
201 }
202 });
203 }
204 }
205
206 try (InputStream is = resourceLoader.getResource(props.getCustomFormLayout()).getInputStream()) {
207 customFormLayout = MAPPER.readValue(is, new TypeReference<>() {
208 });
209 } catch (Exception e) {
210 throw new WicketRuntimeException("Could not read " + props.getCustomFormLayout(), e);
211 }
212
213
214 if (getDebugSettings().isAjaxDebugModeEnabled()) {
215 getDebugSettings().setComponentPathAttributeName("syncope-path");
216 }
217 }
218
219 protected IAuthorizationStrategy getAuthorizationStrategy() {
220 return new AllowAllAuthorizationStrategy() {
221
222 @Override
223 public <T extends IRequestableComponent> boolean isInstantiationAuthorized(final Class<T> componentClass) {
224 if (BasePage.class.isAssignableFrom(componentClass)) {
225 return props.getPage().entrySet().stream().
226 filter(entry -> componentClass.equals(entry.getValue())).
227 map(Map.Entry::getKey).findFirst().
228 map(k -> SyncopeEnduserSession.get().isAuthenticated()).
229 orElse(true);
230 }
231 return true;
232 }
233 };
234 }
235
236 @Override
237 public Class<? extends Page> getHomePage() {
238 return SyncopeEnduserSession.get().isAuthenticated()
239 && SyncopeEnduserSession.get().isMustChangePassword()
240 ? MustChangePassword.class
241 : SyncopeEnduserSession.get().isAuthenticated()
242 ? getPageClass("profile", Dashboard.class)
243 : getSignInPageClass();
244 }
245
246 public ClassPathScanImplementationLookup getLookup() {
247 return lookup;
248 }
249
250 public UserFormLayoutInfo getCustomFormLayout() {
251 return customFormLayout;
252 }
253
254 public Class<? extends Sidebar> getSidebar() {
255 return props.getSidebar();
256 }
257
258 @Override
259 public Session newSession(final Request request, final Response response) {
260 return new SyncopeEnduserSession(request);
261 }
262
263 public SyncopeAnonymousClient newAnonymousClient(final String domain) {
264 return newClientFactory().
265 setDomain(domain).
266 createAnonymous(props.getAnonymousUser(), props.getAnonymousKey());
267 }
268
269 public SyncopeClientFactoryBean newClientFactory() {
270 return new SyncopeClientFactoryBean().
271 setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
272 setUseCompression(props.isUseGZIPCompression());
273 }
274
275 public Class<? extends BasePage> getPageClass(final String name) {
276 return props.getPage().get(name);
277 }
278
279 public Class<? extends BasePage> getPageClass(final String name, final Class<? extends BasePage> defaultValue) {
280 return props.getPage().getOrDefault(name, defaultValue);
281 }
282
283 @Override
284 protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
285 return SyncopeEnduserSession.class;
286 }
287
288 @Override
289 protected Class<? extends WebPage> getSignInPageClass() {
290 return Login.class;
291 }
292
293 public String getAdminUser() {
294 return props.getAdminUser();
295 }
296
297 public String getAnonymousUser() {
298 return props.getAnonymousUser();
299 }
300
301 public boolean isCaptchaEnabled() {
302 return props.isCaptcha();
303 }
304
305 public boolean isReportPropagationErrors() {
306 return props.isReportPropagationErrors();
307 }
308
309 public boolean isReportPropagationErrorDetails() {
310 return props.isReportPropagationErrorDetails();
311 }
312
313 public long getMaxWaitTimeInSeconds() {
314 return props.getMaxWaitTimeOnApplyChanges();
315 }
316
317 public Integer getMaxUploadFileSizeMB() {
318 return props.getMaxUploadFileSizeMB();
319 }
320 }