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 java.security.AccessControlException;
22 import java.text.DateFormat;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.CompletableFuture;
29 import java.util.concurrent.Future;
30 import java.util.stream.Collectors;
31 import javax.ws.rs.BadRequestException;
32 import javax.ws.rs.ForbiddenException;
33 import javax.ws.rs.core.EntityTag;
34 import javax.ws.rs.core.MediaType;
35 import javax.xml.ws.WebServiceException;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.lang3.exception.ExceptionUtils;
38 import org.apache.commons.lang3.time.FastDateFormat;
39 import org.apache.cxf.jaxrs.client.WebClient;
40 import org.apache.syncope.client.lib.SyncopeAnonymousClient;
41 import org.apache.syncope.client.lib.SyncopeClient;
42 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
43 import org.apache.syncope.client.ui.commons.BaseSession;
44 import org.apache.syncope.client.ui.commons.DateOps;
45 import org.apache.syncope.common.lib.SyncopeClientException;
46 import org.apache.syncope.common.lib.SyncopeConstants;
47 import org.apache.syncope.common.lib.info.PlatformInfo;
48 import org.apache.syncope.common.lib.to.UserTO;
49 import org.apache.syncope.common.lib.types.ClientExceptionType;
50 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
51 import org.apache.syncope.common.rest.api.RESTHeaders;
52 import org.apache.wicket.Session;
53 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
54 import org.apache.wicket.authroles.authorization.strategies.role.Roles;
55 import org.apache.wicket.request.Request;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 public class SyncopeEnduserSession extends AuthenticatedWebSession implements BaseSession {
60
61 private static final long serialVersionUID = 747562246415852166L;
62
63 public enum Error {
64 INVALID_SECURITY_ANSWER("invalid.security.answer", "Invalid Security Answer"),
65 SESSION_EXPIRED("error.session.expired", "Session expired: please login again"),
66 AUTHORIZATION("error.authorization", "Insufficient access rights when performing the requested operation"),
67 REST("error.rest", "There was an error while contacting the Core server");
68
69 private final String key;
70
71 private final String fallback;
72
73 Error(final String key, final String fallback) {
74 this.key = key;
75 this.fallback = fallback;
76 }
77
78 public String key() {
79 return key;
80 }
81
82 public String fallback() {
83 return fallback;
84 }
85 }
86
87 protected static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserSession.class);
88
89 public static SyncopeEnduserSession get() {
90 return (SyncopeEnduserSession) Session.get();
91 }
92
93 protected final SyncopeClientFactoryBean clientFactory;
94
95 protected final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<>());
96
97 protected String domain;
98
99 protected SyncopeClient client;
100
101 protected SyncopeAnonymousClient anonymousClient;
102
103 protected UserTO selfTO;
104
105 public SyncopeEnduserSession(final Request request) {
106 super(request);
107
108 clientFactory = SyncopeWebApplication.get().newClientFactory();
109 }
110
111 protected String message(final SyncopeClientException sce) {
112 Error error = null;
113 if (sce.getType() == ClientExceptionType.InvalidSecurityAnswer) {
114 error = Error.INVALID_SECURITY_ANSWER;
115 }
116 if (error == null) {
117 return sce.getType().name() + ": " + sce.getElements().stream().collect(Collectors.joining(", "));
118 }
119 return getApplication().getResourceSettings().getLocalizer().
120 getString(error.key(), null, null, null, null, error.fallback());
121 }
122
123
124
125
126
127
128
129
130
131 @Override
132 public void onException(final Exception e) {
133 Throwable root = ExceptionUtils.getRootCause(e);
134 String message = root.getMessage();
135
136 if (root instanceof SyncopeClientException) {
137 SyncopeClientException sce = (SyncopeClientException) root;
138 message = sce.isComposite()
139 ? sce.asComposite().getExceptions().stream().map(this::message).collect(Collectors.joining("; "))
140 : message(sce);
141 } else if (root instanceof AccessControlException || root instanceof ForbiddenException) {
142 Error error = StringUtils.containsIgnoreCase(message, "expired")
143 ? Error.SESSION_EXPIRED
144 : Error.AUTHORIZATION;
145 message = getApplication().getResourceSettings().getLocalizer().
146 getString(error.key(), null, null, null, null, error.fallback());
147 } else if (root instanceof BadRequestException || root instanceof WebServiceException) {
148 message = getApplication().getResourceSettings().getLocalizer().
149 getString(Error.REST.key(), null, null, null, null, Error.REST.fallback());
150 }
151
152 message = getApplication().getResourceSettings().getLocalizer().
153 getString(message, null, null, null, null, message);
154 error(message);
155 }
156
157 public MediaType getMediaType() {
158 return clientFactory.getContentType().getMediaType();
159 }
160
161 @Override
162 public <T> Future<T> execute(final Callable<T> command) {
163 try {
164 return CompletableFuture.completedFuture(command.call());
165 } catch (Exception e) {
166 LOG.error("Could not execute {}", command, e);
167 }
168 return new CompletableFuture<>();
169 }
170
171 @Override
172 public void setDomain(final String domain) {
173 this.domain = domain;
174 }
175
176 @Override
177 public String getDomain() {
178 return StringUtils.isBlank(domain) ? SyncopeConstants.MASTER_DOMAIN : domain;
179 }
180
181 @Override
182 public String getJWT() {
183 return client == null ? null : client.getJWT();
184 }
185
186 @Override
187 public Roles getRoles() {
188 throw new UnsupportedOperationException("Not supported yet.");
189 }
190
191 public PlatformInfo getPlatformInfo() {
192 return getAnonymousClient().platform();
193 }
194
195 @Override
196 public boolean authenticate(final String username, final String password) {
197 boolean authenticated = false;
198 if (SyncopeWebApplication.get().getAdminUser().equalsIgnoreCase(username)) {
199 return authenticated;
200 }
201
202 try {
203 client = clientFactory.setDomain(getDomain()).create(username, password);
204
205 refreshAuth(username);
206
207 authenticated = true;
208 } catch (Exception e) {
209 LOG.error("Authentication failed", e);
210 }
211
212 return authenticated;
213 }
214
215 public boolean authenticate(final String jwt) {
216 boolean authenticated = false;
217
218 try {
219 client = clientFactory.setDomain(getDomain()).create(jwt);
220
221 refreshAuth(null);
222
223 authenticated = true;
224 } catch (Exception e) {
225 LOG.error("Authentication failed", e);
226 }
227
228 return authenticated;
229 }
230
231 protected void refreshAuth(final String username) {
232 try {
233 anonymousClient = SyncopeWebApplication.get().newAnonymousClient(getDomain());
234
235 selfTO = client.self().getRight();
236 } catch (ForbiddenException e) {
237 LOG.warn("Could not read self(), probably in a {} scenario", IdRepoEntitlement.MUST_CHANGE_PASSWORD, e);
238
239 selfTO = new UserTO();
240 selfTO.setUsername(username);
241 selfTO.setMustChangePassword(true);
242 }
243
244
245
246 this.bind();
247 }
248
249 protected boolean isAuthenticated() {
250 return client != null && client.getJWT() != null;
251 }
252
253 protected boolean isMustChangePassword() {
254 return selfTO != null && selfTO.isMustChangePassword();
255 }
256
257 public void cleanup() {
258 anonymousClient = null;
259
260 client = null;
261 selfTO = null;
262 services.clear();
263 }
264
265 @Override
266 public void invalidate() {
267 if (isAuthenticated()) {
268 try {
269 client.logout();
270 } catch (Exception e) {
271 LOG.debug("Unexpected exception while logging out", e);
272 } finally {
273 client = null;
274 selfTO = null;
275 }
276 }
277 super.invalidate();
278 }
279
280 public UserTO getSelfTO() {
281 return getSelfTO(false);
282 }
283
284 public UserTO getSelfTO(final boolean reload) {
285 if (reload) {
286 refreshAuth(selfTO.getUsername());
287 }
288 return selfTO;
289 }
290
291 @Override
292 public SyncopeAnonymousClient getAnonymousClient() {
293 return Optional.ofNullable(anonymousClient).
294 orElseGet(() -> SyncopeWebApplication.get().newAnonymousClient(getDomain()));
295 }
296
297 @Override
298 public <T> T getAnonymousService(final Class<T> serviceClass) {
299 return getAnonymousClient().getService(serviceClass);
300 }
301
302 @SuppressWarnings("unchecked")
303 private <T> T getCachedService(final Class<T> serviceClass) {
304 T service;
305 if (services.containsKey(serviceClass)) {
306 service = (T) services.get(serviceClass);
307 } else {
308 service = client.getService(serviceClass);
309 services.put(serviceClass, service);
310 }
311
312 WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
313
314 return service;
315 }
316
317 @Override
318 public <T> T getService(final Class<T> serviceClass) {
319 T service = (client == null || !isAuthenticated())
320 ? getAnonymousClient().getService(serviceClass)
321 : client.getService(serviceClass);
322 WebClient.client(service).header(RESTHeaders.DOMAIN, getDomain());
323 return service;
324 }
325
326 @Override
327 public <T> T getService(final String etag, final Class<T> serviceClass) {
328 T serviceInstance = getService(serviceClass);
329 WebClient.client(serviceInstance).match(new EntityTag(etag), false).
330 type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
331
332 return serviceInstance;
333 }
334
335 @Override
336 public <T> void resetClient(final Class<T> service) {
337 T serviceInstance = getCachedService(service);
338 WebClient.client(serviceInstance).reset();
339 }
340
341 @Override
342 public DateOps.Format getDateFormat() {
343 return new DateOps.Format(FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()));
344 }
345 }