Redmine插件如何实现XML任务导入功能

1次阅读

redmine插件中注册xml导入路由需在config/routes.rb添加POST路由并映射到自定义控制器;控制器须继承applicationController、跳过csrf校验、强制校验登录与项目权限;解析XML时禁用外部实体、限制字段与长度、白名单过滤;创建任务应避免create!,改用save(validate: false)配合手动校验和事务包裹;前端需用FormData提交并携带authenticity_token,curl测试须用-F参数;权限上下文(如@project)必须显式传递,防止自定义字段等依赖丢失。

Redmine插件如何实现XML任务导入功能

redmine插件中如何注册XML导入路由和控制器

XML任务导入不是Redmine原生功能,必须通过插件新增控制器处理上传与解析。关键在于让Redmine识别你的路由,且不被权限系统拦截。

  • 在插件的 config/routes.rb 中添加资源路由,例如:
    match 'projects/:project_id/import_tasks_from_xml' => 'import_tasks_from_xml#create', :via => [:post], :as => 'import_tasks_from_xml'
  • 控制器需继承 ApplicationController,并显式声明跳过CSRF保护(因为XML上传常由外部脚本发起):
    skip_before_action :verify_authenticity_token, only: [:create]
  • 必须调用 require_loginfind_project(或手动校验项目权限),否则普通用户可越权导入到任意项目

如何安全解析上传的XML并映射到Redmine issue模型

直接用 Nokogiri::XML 解析用户上传的XML存在XXE和内存爆炸风险,不能无过滤加载外部实体或超大文档。

  • 强制设置解析选项禁用外部实体:
    doc = Nokogiri::XML(params[:xml_file].read) do |config|   config.noent.noenc.noblanks end
  • 只允许白名单字段:如 ,忽略所有未知节点
  • 对每个字段做长度限制(如 subject 截断至 255 字符),防止数据库溢出或日志刷屏
  • Project.find_by_identifier 而非 params[:project_id] 直接查库,避免整数ID被伪造为sql注入载体

为什么用 Issue.create! 容易失败,该用什么替代

create! 会抛出异常中断整个导入流程,而XML通常含多条任务——一条失败不该导致全部回滚,且错误信息需返回给前端供排查。

  • 改用 Issue.new(...).save(validate: false) 配合手动校验,避开Redmine默认的自定义字段/工作流校验(这些在校验阶段可能依赖未初始化的上下文)
  • 对必填字段(如 subject, project_id, author_id)先做空值检查,缺失时记录警告而非报错
  • issue.init_journal(User.current) 显式初始化日志,否则导入的任务不会出现在“活动”流里
  • 批量创建后调用 Issue.transaction { ... } 包裹,但不要把整个XML解析塞进去——Nokogiri对象无法跨事务序列化

前端表单和CSRF绕过时的实际兼容问题

浏览器表单无法直接提交XML文件到Redmine插件接口,常见做法是用javaScript构造FormData,但这会触发CSRF校验失败,除非后端已跳过。

  • 表单必须包含 authenticity_token 字段,即使后端跳过了验证,Rails中间件仍会尝试读取它;漏掉会导致422响应
  • XML文件字段名需与控制器中 params[:xml_file] 一致,建议前端用 FormData.append('xml_file', file)
  • 若用curl测试,注意加 -H "Content-Type: multipart/form-data" 无效——multipart边界由浏览器自动生成,应让curl自动推导:
    curl -F "xml_file=@tasks.xml" -F "authenticity_token=..." http://localhost:3000/projects/demo/import_tasks_from_xml

Redmine插件做XML导入最易被忽略的是权限上下文传递——比如自定义字段值写入时,available_custom_fields 依赖当前 @project 实例,而这个实例在后台线程异步任务中容易丢失。别假设“只要进了控制器就万事大吉”。

text=ZqhQzanResources