com.haulmont.restapi.idp.IdpAuthLifecycleManager.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.restapi.idp.IdpAuthLifecycleManager.java

Source

/*
 * Copyright (c) 2008-2017 Haulmont.
 *
 * Licensed 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 com.haulmont.restapi.idp;

import com.google.gson.Gson;
import com.haulmont.bali.util.URLEncodeUtils;
import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.core.global.Events;
import com.haulmont.cuba.core.sys.ConditionalOnAppProperty;
import com.haulmont.restapi.auth.OAuthTokenRevoker;
import com.haulmont.restapi.events.BeforeRestInvocationEvent;
import com.haulmont.restapi.events.OAuthTokenRevokedResponseEvent;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;

import static com.haulmont.restapi.idp.IdpAuthController.IDP_SESSION_ID_TOKEN_ATTRIBUTE;

/**
 * Component that synchronizes REST API token and IDP session life cycles.
 */
@ConditionalOnAppProperty(property = "cuba.rest.idp.enabled", value = "true")
@Component("cuba_IdpAuthLifecycleManager")
public class IdpAuthLifecycleManager implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(IdpAuthLifecycleManager.class);

    @Inject
    protected Configuration configuration;
    @Inject
    protected OAuthTokenRevoker oAuthTokenRevoker;
    @Inject
    protected TokenStore tokenStore;

    protected RestIdpConfig idpConfig;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.idpConfig = configuration.getConfig(RestIdpConfig.class);
    }

    @Order(Events.HIGHEST_PLATFORM_PRECEDENCE + 100)
    @EventListener
    public void handleBeforeRestInvocationEvent(BeforeRestInvocationEvent event) {
        if (idpConfig.getIdpEnabled()) {
            if (idpConfig.getIdpPingSessionOnRequest()
                    && event.getAuthentication() instanceof OAuth2Authentication) {
                IdpSessionStatus status = pingIdpSession(event.getAuthentication());

                if (status == IdpSessionStatus.EXPIRED) {
                    Object details = event.getAuthentication().getDetails();
                    String accessToken = ((OAuth2AuthenticationDetails) details).getTokenValue();

                    oAuthTokenRevoker.revokeAccessToken(accessToken);

                    log.info("IDP session is expired. REST token {} revoked", accessToken);

                    event.preventInvocation();

                    String idpLoginUrl = getIdpLoginUrl(idpConfig.getIdpDefaultRedirectUrl());
                    Gson gson = new Gson();
                    String body = gson.toJson(new IdpSessionExpiredResponse("idp_session_expired", idpLoginUrl));

                    HttpServletResponse response = (HttpServletResponse) event.getResponse();
                    try {
                        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
                        response.getWriter().write(body);
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    } catch (IOException e) {
                        throw new RuntimeException("Unable to send status to client", e);
                    }
                }
            }
        }
    }

    @Order(Events.HIGHEST_PLATFORM_PRECEDENCE + 100)
    @EventListener
    public void handleOAuthTokenRevocationResponse(OAuthTokenRevokedResponseEvent event) {
        if (idpConfig.getIdpEnabled()) {
            if (event.getTokenValue() != null) {
                log.debug("OAuth2AccessToken {} revoked by client, redirect to IDP", event.getTokenValue());

                String idpLoginUrl = getIdpLogoutUrl(idpConfig.getIdpDefaultRedirectUrl());

                event.setResponseEntity(ResponseEntity.ok(new IdpLogoutResponse(idpLoginUrl)));
            }
        }
    }

    protected String getIdpLoginUrl(String redirectUrl) {
        String idpBaseURL = idpConfig.getIdpBaseURL();
        if (!idpBaseURL.endsWith("/")) {
            idpBaseURL += "/";
        }

        if (redirectUrl == null) {
            return idpBaseURL + "?response_type=client-ticket";
        }

        return idpBaseURL + "?response_type=client-ticket" + "&sp=" + URLEncodeUtils.encodeUtf8(redirectUrl);
    }

    protected String getIdpLogoutUrl(String redirectUrl) {
        String idpBaseURL = idpConfig.getIdpBaseURL();
        if (!idpBaseURL.endsWith("/")) {
            idpBaseURL += "/";
        }

        return idpBaseURL + "logout?response_type=client-ticket" + "&sp=" + URLEncodeUtils.encodeUtf8(redirectUrl);
    }

    protected IdpSessionStatus pingIdpSession(Authentication authentication) {
        if (authentication instanceof OAuth2Authentication) {
            Object details = authentication.getDetails();
            String accessTokenId = ((OAuth2AuthenticationDetails) details).getTokenValue();

            OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenId);
            if (accessToken == null) {
                return IdpSessionStatus.UNSUPPORTED;
            }

            String idpSessionId = getIdpSessionId(accessToken);
            if (idpSessionId == null) {
                return IdpSessionStatus.UNSUPPORTED;
            }

            return pingIdpSessionServer(idpSessionId);
        }

        return IdpSessionStatus.UNSUPPORTED;
    }

    protected String getIdpSessionId(OAuth2AccessToken accessToken) {
        Map<String, Object> details = accessToken.getAdditionalInformation();
        if (details == null) {
            // OAuth2AccessToken does not contain details
            return null;
        }

        return (String) details.get(IDP_SESSION_ID_TOKEN_ATTRIBUTE);
    }

    protected IdpSessionStatus pingIdpSessionServer(String idpSessionId) {
        log.debug("Ping IDP session {}", idpSessionId);

        String idpBaseURL = idpConfig.getIdpBaseURL();
        if (!idpBaseURL.endsWith("/")) {
            idpBaseURL += "/";
        }
        String idpSessionPingUrl = idpBaseURL + "service/ping";

        HttpPost httpPost = new HttpPost(idpSessionPingUrl);
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType());

        UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(
                Arrays.asList(new BasicNameValuePair("idpSessionId", idpSessionId),
                        new BasicNameValuePair("trustedServicePassword", idpConfig.getIdpTrustedServicePassword())),
                StandardCharsets.UTF_8);

        httpPost.setEntity(formEntity);

        HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager();
        HttpClient client = HttpClientBuilder.create().setConnectionManager(connectionManager).build();

        try {
            HttpResponse httpResponse = client.execute(httpPost);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.GONE.value()) {
                log.debug("IDP session expired {}", idpSessionId);

                return IdpSessionStatus.EXPIRED;
            }
            if (statusCode != HttpStatus.OK.value()) {
                log.warn("IDP respond status {} on session ping", statusCode);
            }
        } catch (IOException e) {
            log.warn("Unable to ping IDP {} session {}", idpSessionPingUrl, idpSessionId, e);
        } finally {
            connectionManager.shutdown();
        }

        return IdpSessionStatus.ALIVE;
    }

    public enum IdpSessionStatus {
        ALIVE, UNSUPPORTED, EXPIRED
    }
}