package com.huigou.uasp.bmp.intercept;

import com.huigou.context.ApplicationProperties;
import com.huigou.context.ContextUtil;
import com.huigou.context.Operator;
import com.huigou.uasp.bpm.engine.application.ActApplication;
import com.huigou.uasp.exception.VisitErrorException;
import com.huigou.util.Constants;
import com.huigou.util.Md5Builder;
import com.huigou.util.StringUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author yonghuan
 */
public class DefaultCsrfTokenVerifyStrategy implements CsrfTokenVerifyStrategy {

    private final static Logger LOG = LoggerFactory.getLogger(DefaultCsrfTokenVerifyStrategy.class);
    private final String SPLIT_PATTERN = "[, ;\r\n]";// 逗号 空格 分号 换行

    // csrf验证白名单
    private List<String> csrfWhiteListURL = Collections.emptyList();
    private List<String> ssoCsfWhiteListUrls = Collections.emptyList();
    // 单点登录情况下csrf验证白名单
    private String ssoCsfWhiteListMethodName;
    private final PathMatcher pathMatcher = new AntPathMatcher();
    private ApplicationProperties applicationProperties;
    private ActApplication actApplication;

    public void setCsrfWhiteListURL(List<String> csrfWhiteListURL) {
        this.csrfWhiteListURL = csrfWhiteListURL;
    }

    public void setSsoCsfWhiteListUrls(List<String> ssoCsfWhiteListUrls) {
        this.ssoCsfWhiteListUrls = ssoCsfWhiteListUrls;
    }

    public void setSsoCsfWhiteListMethodName(String ssoCsfWhiteListMethodName) {
        this.ssoCsfWhiteListMethodName = ssoCsfWhiteListMethodName;
    }

    public void setApplicationProperties(ApplicationProperties applicationProperties) {
        this.applicationProperties = applicationProperties;
    }

    public void setActApplication(ActApplication actApplication) {
        this.actApplication = actApplication;
    }

    @Override
    public boolean verify(HttpServletRequest request, Operator operator) {
        // 处理跨站请求伪造
        boolean verifyCSRF = this.verifyCSRFToken(request);
        // 跨站请求伪造校验失败
        if (!verifyCSRF) {
            // 全部ajax请求抛出异常
            if (ContextUtil.isAjaxRequest()) {
                throw new VisitErrorException();
            }
            // CAS中 处理可以直接产生数据改变的服务
            if (isCas()) {
                // 只有 ssoCsfWhiteListMethodName 中开头的方法放行
                verifyCSRF = this.isSSOCSRFWhiteURL(request.getServletPath());
                if (verifyCSRF) {
                    this.verifySSOAsBizId(request, operator);
                }
            }
        }
        return verifyCSRF;
    }

    private boolean verifyCSRFToken(HttpServletRequest request) {
        // 白名单中的url不进行CSRFToken校验
        if (this.isCSRFWhiteURL(request.getServletPath())) {
            return true;
        }
        if (ContextUtil.isAppRequest()) {
            return true;
        }
        // 从 head中取 ajax 操作可获取
        String csrfToken = request.getHeader(Constants.CSRF_TOKEN);
        // 读取请求中参数
        if (StringUtil.isBlank(csrfToken)) {
            csrfToken = request.getParameter(Constants.CSRF_TOKEN);
        }
        if (StringUtil.isBlank(csrfToken)) {
            return false;
        }
        Subject subject = SecurityUtils.getSubject();
        String sessionCSRFToken = (String) subject.getSession().getAttribute(Constants.CSRF_TOKEN);
        if (StringUtil.isBlank(sessionCSRFToken)) {
            return false;
        }
        String checkCSRFToken = sessionCSRFToken;
        String bizId = request.getParameter(Constants.BIZID);
        if (StringUtil.isNotBlank(bizId)) {// 存在bizId
            if (!ContextUtil.isAjaxRequest()) {// 不是ajax请求
                // 存在bizId校验则使用bizId与token加密后传输
                checkCSRFToken = Md5Builder.getMd5(bizId + sessionCSRFToken);
            }
        }
        if (csrfToken.equalsIgnoreCase(checkCSRFToken)) {
            return true;
        } else {
            LOG.error("存在csrfToken的不相同, csrfToken={},sessionCSRFToken={}", csrfToken, checkCSRFToken);
            return false;
        }
    }

    /**
     * csrf白名单验证
     *
     * @param currentURL
     * @return
     */
    private boolean isCSRFWhiteURL(String currentURL) {
        if (csrfWhiteListURL == null || csrfWhiteListURL.size() == 0) {
            return false;
        }
        for (String whiteURL : csrfWhiteListURL) {
            if (pathMatcher.match(whiteURL, currentURL)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 单点登录情况下 csrf白名单验证
     *
     * @param currentURL
     * @return
     */
    private boolean isSSOCSRFWhiteURL(String currentURL) {
        List<String> whiteList = this.getSsoCsfWhiteListUrls();
        if (whiteList == null || whiteList.size() == 0) {
            return false;
        }
        for (String whiteURL : whiteList) {
            if (pathMatcher.match(whiteURL, currentURL)) {
                return true;
            }
        }
        return false;
    }

    private List<String> getSsoCsfWhiteListUrls() {
        if (ssoCsfWhiteListUrls == null) {
            // 解析字符串为可用于匹配的路径
            ssoCsfWhiteListUrls = new ArrayList<String>();
            if (StringUtil.isNotBlank(ssoCsfWhiteListMethodName)) {
                String[] nameArray = ssoCsfWhiteListMethodName.split(SPLIT_PATTERN);
                for (String name : nameArray) {
                    name = name.trim();
                    if (name.length() == 0) {
                        continue;
                    }
                    // 如字符串为 load 匹配路径为/**/load*
                    ssoCsfWhiteListUrls.add(String.format("/**/%s*", name));
                }
            }
        }
        return ssoCsfWhiteListUrls;
    }

    /**
     * 单点登录链接校验
     */
    private void verifySSOAsBizId(HttpServletRequest request, Operator operator) {
        String bizId = request.getParameter(Constants.BIZID);
        if (StringUtil.isBlank(bizId)) {
            return;
        }
        // 存在bizId 需要根据当前人员判断是否允许查看任务单据
        boolean flag = actApplication.checkVisitTaskByBizIdAndPersonId(bizId, operator.getUserId());
        if (!flag) {
            throw new AuthorizationException();
        }
    }

    private boolean isCas() {
        return applicationProperties.isCas();
    }

}
