/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.client.console;
import java.text.DateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.info.PlatformInfo;
import org.apache.syncope.common.lib.info.SystemInfo;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.rest.api.service.SyncopeService;
import org.apache.wicket.Session;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.request.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
public class SyncopeConsoleSession extends AuthenticatedWebSession {
private static final long serialVersionUID = 747562246415852166L;
private static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleSession.class);
private static final ThreadPoolExecutorFactoryBean THREAD_POOL_FACTORY;
public static final String AUTHENTICATED = "AUTHENTICATED";
public static final String MENU_COLLAPSE = "MENU_COLLAPSE";
static {
THREAD_POOL_FACTORY = new ThreadPoolExecutorFactoryBean();
THREAD_POOL_FACTORY.setThreadNamePrefix(SyncopeConsoleSession.class.getSimpleName());
THREAD_POOL_FACTORY.setDaemon(true);
}
private final SyncopeClientFactoryBean clientFactory;
private final SyncopeClient anonymousClient;
private final PlatformInfo platformInfo;
private final SystemInfo systemInfo;
private String domain;
private final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<Class<?>, Object>());
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5, THREAD_POOL_FACTORY);
private SyncopeClient client;
private UserTO selfTO;
private Map<String, Set<String>> auth;
private Roles roles;
public static SyncopeConsoleSession get() {
return (SyncopeConsoleSession) Session.get();
}
public SyncopeConsoleSession(final Request request) {
super(request);
clientFactory = SyncopeConsoleApplication.get().newClientFactory();
anonymousClient = clientFactory.
create(new AnonymousAuthenticationHandler(
SyncopeConsoleApplication.get().getAnonymousUser(),
SyncopeConsoleApplication.get().getAnonymousKey()));
platformInfo = anonymousClient.getService(SyncopeService.class).platform();
systemInfo = anonymousClient.getService(SyncopeService.class).system();
}
public MediaType getMediaType() {
return clientFactory.getContentType().getMediaType();
}
public SyncopeClient getAnonymousClient() {
return anonymousClient;
}
public void execute(final Runnable command) {
executorService.execute(command);
}
public PlatformInfo getPlatformInfo() {
return platformInfo;
}
public SystemInfo getSystemInfo() {
return systemInfo;
}
public void setDomain(final String domain) {
this.domain = domain;
}
public String getDomain() {
return StringUtils.isBlank(domain) ? SyncopeConstants.MASTER_DOMAIN : domain;
}
public String getJWT() {
return client == null ? null : client.getJWT();
}
private void afterAuthentication() {
Pair<Map<String, Set<String>>, UserTO> self = client.self();
auth = self.getKey();
selfTO = self.getValue();
}
@Override
public boolean authenticate(final String username, final String password) {
boolean authenticated = false;
try {
client = clientFactory.setDomain(getDomain()).create(username, password);
afterAuthentication();
authenticated = true;
} catch (Exception e) {
LOG.error("Authentication failed", e);
}
return authenticated;
}
public boolean authenticate(final String jwt) {
boolean authenticated = false;
try {
client = clientFactory.setDomain(getDomain()).create(jwt);
afterAuthentication();
authenticated = true;
} catch (Exception e) {
LOG.error("Authentication failed", e);
}
if (authenticated) {
bind();
}
signIn(authenticated);
return authenticated;
}
public void cleanup() {
client = null;
auth = null;
selfTO = null;
}
@Override
public void invalidate() {
if (getJWT() != null) {
if (client != null) {
client.logout();
}
cleanup();
}
executorService.shutdown();
super.invalidate();
}
@Override
public void invalidateNow() {
if (getJWT() != null) {
if (client != null) {
client.logout();
}
cleanup();
}
executorService.shutdownNow();
super.invalidateNow();
}
public UserTO getSelfTO() {
return selfTO;
}
public Set<String> getAvailableRealms(final String... entitlements) {
final Set<String> availableRealms = new HashSet<>();
if (entitlements != null && entitlements.length > 0) {
for (String entitlement : entitlements) {
final Set<String> realms = auth.get(entitlement);
if (CollectionUtils.isNotEmpty(realms)) {
availableRealms.addAll(realms);
}
}
} else {
for (Map.Entry<String, Set<String>> entitlement : auth.entrySet()) {
availableRealms.addAll(entitlement.getValue());
}
}
return availableRealms;
}
public boolean owns(final String entitlements) {
return owns(entitlements, "/");
}
public boolean owns(final String entitlements, final String realm) {
if (StringUtils.isEmpty(entitlements)) {
return true;
}
for (String entitlement : entitlements.split(",")) {
if (auth != null && auth.containsKey(entitlement)
&& (realm == null || IterableUtils.matchesAny(auth.get(entitlement), new Predicate<String>() {
@Override
public boolean evaluate(final String ownedRealm) {
return realm.startsWith(ownedRealm);
}
}))) {
return true;
}
}
return false;
}
@Override
public Roles getRoles() {
if (isSignedIn() && roles == null && auth != null) {
roles = new Roles(auth.keySet().toArray(new String[] {}));
roles.add(AUTHENTICATED);
}
return roles;
}
public void refreshAuth() {
client.refresh();
roles = null;
}
@SuppressWarnings("unchecked")
private <T> T getCachedService(final Class<T> serviceClass) {
T service;
if (services.containsKey(serviceClass)) {
service = (T) services.get(serviceClass);
} else {
service = client.getService(serviceClass);
services.put(serviceClass, service);
}
WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
return service;
}
public <T> T getService(final Class<T> serviceClass) {
return getCachedService(serviceClass);
}
public <T> T getService(final String etag, final Class<T> serviceClass) {
T serviceInstance = getCachedService(serviceClass);
WebClient.client(serviceInstance).match(new EntityTag(etag), false);
return serviceInstance;
}
public <T> T getService(final MediaType mediaType, final Class<T> serviceClass) {
T service;
synchronized (clientFactory) {
SyncopeClientFactoryBean.ContentType preType = clientFactory.getContentType();
clientFactory.
setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString()));
service = clientFactory.create(getJWT()).getService(serviceClass);
clientFactory.setContentType(preType);
}
return service;
}
public <T> void resetClient(final Class<T> service) {
T serviceInstance = getCachedService(service);
WebClient.client(serviceInstance).reset();
}
public FastDateFormat getDateFormat() {
Locale locale = getLocale() == null ? Locale.ENGLISH : getLocale();
return FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
}
}