All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.polyglotted.spring.elastic.ElasticProcessor Maven / Gradle / Ivy

package io.polyglotted.spring.elastic;

import io.polyglotted.common.model.AuthToken;
import io.polyglotted.common.model.Subject;
import io.polyglotted.common.util.HttpConfig;
import io.polyglotted.common.util.ListBuilder;
import io.polyglotted.spring.elastic.ElasticAuthFilter.ElasticClientException;
import io.polyglotted.spring.security.DefaultAuthToken;
import io.polyglotted.spring.web.SimpleResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;
import java.io.Closeable;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;
import java.util.List;

import static io.polyglotted.common.util.CollUtil.fluent;
import static io.polyglotted.common.util.HttpConfig.httpClient;
import static io.polyglotted.common.util.HttpUtil.buildDelete;
import static io.polyglotted.common.util.HttpUtil.buildGet;
import static io.polyglotted.common.util.HttpUtil.buildPost;
import static io.polyglotted.common.util.HttpUtil.execute;
import static io.polyglotted.common.util.ListBuilder.immutableList;
import static io.polyglotted.common.util.ResourceUtil.urlResource;
import static io.polyglotted.common.util.StrUtil.notNullOrEmpty;
import static io.polyglotted.common.util.StrUtil.safePrefix;
import static io.polyglotted.common.util.ThreadUtil.safeSleep;
import static io.polyglotted.spring.errorhandling.ExceptionFactory.checkBadRequest;
import static io.polyglotted.spring.errorhandling.ExceptionFactory.unauthorisedException;
import static lombok.AccessLevel.PRIVATE;
import static org.apache.http.HttpHeaders.AUTHORIZATION;

@SuppressWarnings("unused") @Slf4j @Component @RequiredArgsConstructor(access = PRIVATE)
public class ElasticProcessor implements Closeable {
    private static final String LOGIN_TEMPL = "{\"grant_type\":\"password\",\"username\":\"$userid\",\"password\":\"$passwd\"}";
    private static final String LOGOUT_TEMPL = "{\"token\":\"$token\"}";
    private final CloseableHttpClient httpClient;
    private final String baseUri;

    @Autowired public ElasticProcessor(@Qualifier("idpEsHttpConfig") HttpConfig config) {
        this(createHttpClient(config), config.url());
    }

    @PreDestroy @Override public void close() throws IOException { httpClient.close(); }

    public AuthToken login(String userId, String password) {
        checkBadRequest(notNullOrEmpty(userId) && notNullOrEmpty(password), "Invalid credentials.");
        try {
            return AuthToken.buildWith(execute(httpClient, buildPost(baseUri, "/_xpack/security/oauth2/token").withBasicAuth(userId, password)
                .withJson(LOGIN_TEMPL.replace("$userid", userId).replace("$passwd", password))));
        } catch (Exception ex) {
            ex.printStackTrace();
            log.debug("not found or invalid creds: {}", userId); throw unauthorisedException(safePrefix(ex.getMessage(), " ("));
        }
    }

    public SimpleResponse logout(String token) {
        try {
            execute(httpClient, buildDelete(baseUri, "/_xpack/security/oauth2/token").withBearerAuth(token)
                .withJson(LOGOUT_TEMPL.replace("$token", token)));
        } catch (Exception ex) { log.warn("failed to logout " + token); }
        return new SimpleResponse("logged-out");
    }

    DefaultAuthToken authenticate(HttpServletRequest request) {
        String header = request.getHeader(AUTHORIZATION);
        if (header != null) {
            if (header.startsWith("Basic") || (header.startsWith("Bearer") && !header.contains("."))) {
                Subject subject = authenticateInternal(header);
                return new DefaultAuthToken(subject, header, authorities(subject.roles));
            }
            log.trace("no Bearer token or Basic Auth found in AUTHORISATION header");
        }
        log.trace("AUTHORISATION header not found");
        return null;
    }

    private Subject authenticateInternal(String authHeader) {
        try {
            return Subject.buildWith(execute(httpClient, buildGet(baseUri, "/_xpack/security/_authenticate").withAuth(authHeader)));
        } catch (Exception ex) { throw new ElasticClientException("failed to authenticate"); }
    }

    private static List authorities(List roles) {
        return ListBuilder.immutableListBuilder()
            .add(new SimpleGrantedAuthority("ROLE_CONSUMER"))
            .addAll(fluent(roles).transformAndConcat(ElasticProcessor::transformRole))
            .build();
    }

    private static List transformRole(String role) {
        return !"superuser".equals(role) ? immutableList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())) : immutableList(
            new SimpleGrantedAuthority("ROLE_CURATOR"),
            new SimpleGrantedAuthority("ROLE_MODELER"),
            new SimpleGrantedAuthority("ROLE_GATE_KEEPER"),
            new SimpleGrantedAuthority("ROLE_ADMINISTRATOR")
        );
    }

    @SuppressWarnings({"ConstantConditions"})
    private static CloseableHttpClient createHttpClient(HttpConfig config) {
        for (int i = 0; i <= 300; i++) {
            try {
                URL trustStore = urlResource(ElasticProcessor.class, "elastic-spring-ca.p12");
                log.debug("connecting to " + config.url()); return httpClient(config.setTrustStore(trustStore.toString()));
            } catch (Exception ioe) {
                if (ioe instanceof ConnectException) { safeSleep(1000); }
                else { throw ioe; }
            }
        }
        throw new IllegalStateException("create elasticProcesor client failed");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy