# 附件集成

在附件上传和下载时提供拦截机制.

杀毒:附件上传时结合杀毒软件厂商提供的接口,对文件进行查毒和杀毒。 加密:附件上传下载时使用特定的加密算法对附件进行加解密。 舆情监控:扫描上传附件的内容,禁止含特定关键字的附件进入系统。 审计日志:对附件的上传和下载行为进行记录。

# 接口调用方式

需要在OA Java主程序中调用本接口,依赖OA中相关实例。

# 1.1附件上传

通过监听FileUploadEvent事件可以拦截附件上传进行处理。

适用于对附件进行查毒杀毒或加密等场景。

@ListenEvent(event = FileUploadEvent.class)
    public void onFileUpload(FileUploadEvent evt) throws Exception {
        // 取得上传的文件
        FileItem fileItem = evt.getFileItem();
        // doSth
    }

如果要终止上传,可以抛异常:throw new BusinessException,此时,上传被取消,前端提示错误,错误信息为异常的message。

如果要更改上传文件的内容,取得Event中的FileItem对象,调用setInputStream设置输入流,此时,保存到磁盘的文件被更改,上传成功。

如果想不终止上传,但给上传完毕要给客户端用户提示时,使用FileItem的appendMessage设置提示。

# 1.2附件下载

通过监听FileDownEvent事件可以拦截附件下载进行处理。

适用于对附件进行加密和解密等场景。

@ListenEvent(event = FileDownloadEvent.class)
    public void onFileDownload(FileDownloadEvent evt) throws Exception {
        // 取得下载的文件
        V3XFile file = evt.getFile();
        // doSth
    }

# 1.3附件解密

系统中存储的附件都按设置的加密算法进行了加密,如果要手动对存储的附件进行解密,可以调用CoderFactory.getInstance().download方法(依赖seeyon\WEB-INF\lib\seeyon_ctp_core.jar),如下所示。

import com.seeyon.ctp.common.encrypt.CoderFactory;
    /**
     * 解密附件。
     * @param input 附件文件对象
     * @param output 解密后的附件
     * @throws Exception
     */
    public void decrypt(File input, File output) throws Exception {
        FileOutputStream fop = null;
        FileInputStream fin = null;
        try {
            fop = new FileOutputStream(output);
            fin = new FileInputStream(input);
            CoderFactory.getInstance().download(fin, fop);
            fop.flush();
            fop.close();
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                if (fop != null) {
                    fop.close();
                }
                if (fin != null) {
                    fin.close();
                }
            } catch (IOException e) {

            }
        }
    }

# 1.4附件加密

第三方系统可以调用CoderFactory.getInstance().upload方法(依赖seeyon\WEB-INF\lib\seeyon_ctp_core.jar)对文件进行加密。

 import com.seeyon.ctp.common.encrypt.CoderFactory;
    import com.seeyon.ctp.common.encrypt.ICoder;
    /**
     * 加密文件。
     * 
     * @param input
     *            要加密的文件输入流
     * @param output
     *            加密后的文件输出流
     * @param encryptVersion
     *            可选值为轻度加密:ICoder.VERSON01;深度加密:ICoder.VERSON02;不加密:no
     * @throws Exception
     */
    public void encrypt(FileInputStream in, FileOutputStream out
            ,String encryptVersion) throws Exception {
        CoderFactory.getInstance().upload(in, out, encryptVersion);
    }

# 1.5示例


package com.seeyon.apps.test.listener;

import java.io.ByteArrayInputStream;

import javax.servlet.http.HttpServletResponse;
import com.seeyon.ctp.common.exceptions.BusinessException;
import com.seeyon.ctp.common.filemanager.event.FileDownloadEvent;
import com.seeyon.ctp.common.filemanager.event.FileItem;
import com.seeyon.ctp.common.filemanager.event.FileUploadEvent;
import com.seeyon.ctp.common.po.filemanager.V3XFile;
import com.seeyon.ctp.util.annotation.ListenEvent;
// 
public class FileEventListener {
    // 附件上传监听示例:杀毒
    @ListenEvent(event = FileUploadEvent.class)
    public void onFileUpload(FileUploadEvent evt) throws Exception {
        FileItem fileItem = evt.getFileItem();
        String originalFilename = fileItem.getOriginalFilename();
        if (originalFilename.contains("有毒无法杀")) {
            // 文件有病毒,但杀毒失败,此时抛出异常,提示用户
            throw new BusinessException(originalFilename + "有病毒,无法清除");
        } else if (originalFilename.contains("有毒已杀")) {
            // 文件有病毒,杀毒成功,保存清除病毒后的文件,然后提示用户
            fileItem.setInputStream(new ByteArrayInputStream("virus clean"
                    .getBytes()));
            fileItem.appendMessage(originalFilename + "病毒已清除");
        }
    }
    // 附件下载监听示例:加密
    @ListenEvent(event = FileDownloadEvent.class)
    public void onFileDownload(FileDownloadEvent evt) throws Exception {
            V3XFile file = evt.getFile();
            if(file==null){
                return;
            }
            // 筛选要处理的文件
            if("application/vnd.ms-excel".equals(file.getMimeType())){
                HttpServletResponse response = evt.getResponse();
                response.setContentType("application/vnd.ms-excel; charset=UTF-8");
                response.setHeader("Content-disposition", "");
                // 压缩或加密文件
                evt.setInputStream(new ByteArrayInputStream("my file"
                        .getBytes()));
            }

    }
}

# 查找返回V3XFile对象

Since:V7.0

查找返回V3XFile对象。

接口请求说明

http请求方式:GET
http://ip:port/seeyon/rest/doc/getctpfile/{ctpFileId}
例如:
http://127.0.0.1:8080/seeyon/rest/doc/getctpfile/-7885545461126487774

DEMO:

    CTPRestClient client = RestResource.getInstance().resouresClent();
    String token = RestResource.getInstance().getToken(client);
    client.get("doc/getctpfile/-7885545461126487774",String.class);

返回说明

获取成功,会返回{"code":"0","data":"V3XFile的JSON的Object对象"}

# Rest附件相关接口变动

Since:V9.1 0710 由于安全问题,对于如下接口做了安全整改

接口 接口说明 处理策略
/rest/attachment/digestv 获取文件的v 删除,接口不可用
/rest/attachment/removeFile/{fileId} 删除附件 删除,接口不可用
/rest/attachment/renameFile/{fileId}/{newName} 附件重命名 删除,接口不可用
/rest/attachment/{attachmentId} 获取附件信息 去掉v属性返回
/rest/attachment/file/{fileId} 下载附件 增加校验
/rest/attachment/file/batch/{fileIds} 批量下载附件 增加校验
/rest/attachment/fileforuc/{fileId} 致信附件下载 增加校验
/rest/attachment/file/batchDownload/{fileIds} 批量下载附件 增加校验

对于“增加校验”的接口。附件下载接口增加了如下校验过程

  • a.首先通过查询参数是否有v,如果带有,则通过根据digest(fileId, seed)计算是否与v参数一致
  • b.如果参数没有v,则查看是否当前session是否绑定fileId,如果绑定了该fileId,则校验通过
  • c.如果a/b两步都没通过,则通过对应fileId的category找到业务校验接口,业务校验接口实现该文件校验,目前只有致信实现了该接口,用于对M3上致信聊天记录中的附件进行下载。

上述a)b)c)三步校验来确保附件下载越权检测

如果使用a策略的校验,需要前端页面请求增加v的查询参数,例如对于rest/attachment/file的下载

例如:
http://127.0.0.1:8080/seeyon/rest/attachment/file/-7885545461126487774?v=32454315235r51

后端业务调用点,如果是通过FileManager获取的com.seeyon.ctp.common.po.filemanager.V3XFile对象,

package com.seeyon.ctp.common.po.filemanager;

import java.io.Serializable;


/**
 *
 * @author <a href="mailto:tanmf@seeyon.com">Tanmf</a>
 * @version 1.0 2006-11-15
 *
 * @hibernate.class table="v3x_file_mapping"
 */
public class V3XFile extends BasePO implements Serializable {

    private String         v;

    public String getV() {
        if (v == null) {
            //OA-131736:移动端上传的文件type可能为null
            if(id!=null){
                v = SecurityHelper.digest(id);
            }
        }
        return v;
    }

或者通过AttachmentManager获取到的com.seeyon.ctp.common.po.filemanager.Attachment对象,

package com.seeyon.ctp.common.po.filemanager;

/**
 *
 *
 * @author <a href="mailto:tanmf@seeyon.com">Tanmf</a>
 * @version 1.0 2006-11-15
 */
public class Attachment extends BasePO implements Serializable {



    public String getV() {
        if (v == null) {
            if (type == null) {
                //OA-131736:移动端上传的文件type可能为null
                if (fileUrl != null) {
                    //OA-132254:PC端处理协同时,插入附件后,点击【提交】协同仍在待办中(修改问题引起的新问题)
                    v = SecurityHelper.digest(fileUrl);
                }
            } else {
                switch (type) {
                    case 0:
                    case 1:
                    case 3:
                    case 5:
                        //附件情况下的安全v串生成规则
                        if (fileUrl != null) {
                            v = SecurityHelper.digest(fileUrl);
                        }
                        break;
                    case 2:
                    case 4:
                        //关联文档情况下的安全v串生成规则
                        if (description != null && reference != null) {
                            v = SecurityHelper.digest(description, reference);
                        }
                        break;
                }
            }
        }
        return v;
    }


这两个对象本来是提供了getV方法,所以业务只需要在业务VO对象里返回附件对象里的v即可。如果业务后端没有调用过FileManager或者AttachmentManager,而是直接通过读取自己业务存储里获取到的fileId,则可以直接在返回VO对象里,通过自己通过com.seeyon.ctp.common.security.SecurityHelper#digest来计算v参数属性,如下


    public String getV() {
        if (v == null) {
            //OA-131736:移动端上传的文件type可能为null
            if(id!=null){
                v = SecurityHelper.digest(id);
            }
        }
        return v;
    }

如果使用b策略的校验,只需要改动后端代码,但是需要梳理清楚,业务前端在什么地方通过后端什么接口获取到的附件列表。 在该后端接口处,使用com.seeyon.ctp.rest.util.SessionFileIdsUtil#add,将fileId绑定到session中。代码样例如下:

1751878904576.png

编撰人:admin、zhuhui、het、lichaoj