Antonio Espinosa
9 years ago
committed by
Pedro M. Baeza
21 changed files with 1069 additions and 0 deletions
-
116report_qweb_signer/README.rst
-
5report_qweb_signer/__init__.py
-
31report_qweb_signer/__openerp__.py
-
21report_qweb_signer/demo/report_certificate.xml
-
46report_qweb_signer/demo/report_partner.xml
-
161report_qweb_signer/i18n/es.po
-
7report_qweb_signer/models/__init__.py
-
193report_qweb_signer/models/report.py
-
42report_qweb_signer/models/report_certificate.py
-
14report_qweb_signer/models/res_company.py
-
3report_qweb_signer/security/ir.model.access.csv
-
BINreport_qweb_signer/static/certificate/test.p12
-
1report_qweb_signer/static/certificate/test.passwd
-
BINreport_qweb_signer/static/description/icon.png
-
1report_qweb_signer/static/description/noun_65694_cc.svg
-
BINreport_qweb_signer/static/jar/itext-1.4.8.jar
-
BINreport_qweb_signer/static/jar/jPdfSign.jar
-
333report_qweb_signer/static/src/java/JPdfSign.java
-
3report_qweb_signer/static/src/java/strings.properties
-
66report_qweb_signer/views/report_certificate_view.xml
-
26report_qweb_signer/views/res_company_view.xml
@ -0,0 +1,116 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
======================= |
||||
|
Qweb PDF reports signer |
||||
|
======================= |
||||
|
|
||||
|
This module extends the functionality of report module to sign |
||||
|
PDFs using a PKCS#12 certificate. |
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
To install this module, you need to install Java JDK:: |
||||
|
|
||||
|
apt-get install openjdk-7-jre-headless |
||||
|
|
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
In order to start signing PDF documents you need to configure certificate(s) |
||||
|
to use in your company. |
||||
|
|
||||
|
* Go to ``Settings > Companies > Companies > Your company`` |
||||
|
* Go to ``Report configuration`` tab |
||||
|
* Click ``Edit`` |
||||
|
* Add a new item in ``PDF report certificates`` list |
||||
|
* Click ``Create`` |
||||
|
* Set name, certificate file, password file and model |
||||
|
* Optionally you can set a domain and filename pattern for saving as attachment |
||||
|
|
||||
|
For example, if you want to sign only customer invoices in open or paid state: |
||||
|
|
||||
|
* Model: ``account.invoice`` |
||||
|
* Domain: ``[('type','=','out_invoice'), ('state', 'in', ('open', 'paid'))]`` |
||||
|
* Save as attachment: ``(object.number or '').replace('/','_') + '.signed.pdf'`` |
||||
|
|
||||
|
**Note**: Linux user that executes Odoo server process must have |
||||
|
read access to certificate file and password file |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
User just prints PDF documents (only Qweb PDF reports supported) as usual, |
||||
|
but signed PDF is automatically downloaded if this document model is configured |
||||
|
as indicated above. |
||||
|
|
||||
|
If 'Save as attachment' is configured, signed PDF is saved as attachment and |
||||
|
next time saved one is downloaded without signing again. This is appropiate when |
||||
|
signing date is important, for example, when signing customer invoices. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/143/8.0 |
||||
|
|
||||
|
For further information, please visit: |
||||
|
|
||||
|
* https://www.odoo.com/forum/help-1 |
||||
|
|
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* When signing multiple documents (if 'Allow only one document' is disable) |
||||
|
then 'Save as attachment' is not applied and signed result is not |
||||
|
saved as attachment. |
||||
|
|
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/reporting-engine/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback |
||||
|
`here <https://github.com/OCA/reporting-engine/issues/new?body=module:%20report_qweb_signer%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
||||
|
|
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
External utilities |
||||
|
------------------ |
||||
|
|
||||
|
* iText v1.4.8: © 2000-2006, Paulo Soares, Bruno Lowagie and others - License `MPL <http://www.mozilla.org/MPL>`_ or `LGPL2 <http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html>`_ - http://sourceforge.net/projects/itext |
||||
|
* jPdfSign: © 2006 Jan Peter Stotz - License `MPL <http://www.mozilla.org/MPL>`_ or `LGPL2 <http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html>`_ (inherited from iText) - http://private.sit.fraunhofer.de/~stotz/software/jpdfsign |
||||
|
* Modified jPdfSign: © 2015 Antonio Espinosa - License `MPL <http://www.mozilla.org/MPL>`_ or `LGPL2 <http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html>`_ (inherited from iText) - static/src/java/JPdfSign.java |
||||
|
|
||||
|
Icon |
||||
|
---- |
||||
|
|
||||
|
`Created by Anton Noskov from the Noun Project <https://thenounproject.com/search/?q=signed+contract&i=65694>`_ |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Rafael Blasco <rafabn@antiun.com> |
||||
|
* Antonio Espinosa <antonioea@antiun.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from . import models |
@ -0,0 +1,31 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
{ |
||||
|
"name": "Qweb PDF reports signer", |
||||
|
"summary": "Sign Qweb PDFs usign a PKCS#12 certificate", |
||||
|
"version": "8.0.1.0.0", |
||||
|
"category": "Reporting", |
||||
|
"website": "http://www.antiun.com", |
||||
|
"author": "Antiun Ingeniería S.L., " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
"license": "AGPL-3", |
||||
|
"application": False, |
||||
|
"installable": True, |
||||
|
"depends": [ |
||||
|
"report", |
||||
|
], |
||||
|
"external_dependencies": { |
||||
|
"bin": ['/usr/bin/java'], |
||||
|
}, |
||||
|
"data": [ |
||||
|
"security/ir.model.access.csv", |
||||
|
"views/report_certificate_view.xml", |
||||
|
"views/res_company_view.xml", |
||||
|
], |
||||
|
"demo": [ |
||||
|
"demo/report_partner.xml", |
||||
|
"demo/report_certificate.xml", |
||||
|
], |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
© 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
<openerp> |
||||
|
<data noupdate="1"> |
||||
|
|
||||
|
<record id="demo_certificate_test" model="report.certificate"> |
||||
|
<field name="company_id" ref="base.main_company"/> |
||||
|
<field name="name">Test OCA certificate</field> |
||||
|
<field name="path">test.p12</field> |
||||
|
<field name="password_file">test.passwd</field> |
||||
|
<field name="model_id" ref="base.model_res_partner"/> |
||||
|
<field name="domain">[('customer', '=', True)]</field> |
||||
|
<field name="allow_only_one" eval="True"/> |
||||
|
<field name="attachment">'test_' + (object.name or '').replace(' ', '_').lower() + '.signed.pdf'</field> |
||||
|
</record> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,46 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
© 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<template id="report_partner_demo_document"> |
||||
|
<t t-call="report.external_layout"> |
||||
|
<div class="page"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-12"> |
||||
|
This is a sample report for testing PDF certificates |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-12"> |
||||
|
<strong>Partner:</strong> <span t-field="o.name"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</template> |
||||
|
|
||||
|
<template id="report_partner_demo"> |
||||
|
<t t-call="report.html_container"> |
||||
|
<t t-foreach="doc_ids" t-as="doc_id"> |
||||
|
<t t-raw="translate_doc(doc_id, doc_model, 'lang', 'report_qweb_signer.report_partner_demo_document')"/> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
|
||||
|
<report |
||||
|
id="partner_demo" |
||||
|
model="res.partner" |
||||
|
string="Test PDF certificate" |
||||
|
report_type="qweb-pdf" |
||||
|
name="report_qweb_signer.report_partner_demo" |
||||
|
file="report_qweb_signer.report_partner_demo" |
||||
|
attachment_use="True" |
||||
|
attachment="'test_' + (object.name or '').replace(' ', '_').lower() + '.pdf'" |
||||
|
/> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,161 @@ |
|||||
|
# Translation of Odoo Server. |
||||
|
# This file contains the translation of the following modules: |
||||
|
# * report_qweb_signer |
||||
|
# |
||||
|
msgid "" |
||||
|
msgstr "" |
||||
|
"Project-Id-Version: Odoo Server 8.0\n" |
||||
|
"Report-Msgid-Bugs-To: \n" |
||||
|
"POT-Creation-Date: 2015-11-22 19:28+0000\n" |
||||
|
"PO-Revision-Date: 2015-11-22 19:28+0000\n" |
||||
|
"Last-Translator: <>\n" |
||||
|
"Language-Team: \n" |
||||
|
"MIME-Version: 1.0\n" |
||||
|
"Content-Type: text/plain; charset=UTF-8\n" |
||||
|
"Content-Transfer-Encoding: \n" |
||||
|
"Plural-Forms: \n" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,allow_only_one:0 |
||||
|
msgid "Allow only one document" |
||||
|
msgstr "Sólo un documento" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,path:0 |
||||
|
msgid "Certificate file path" |
||||
|
msgstr "Ruta al certificado" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: view:res.company:report_qweb_signer.view_company_form |
||||
|
msgid "Certificates" |
||||
|
msgstr "Certificados" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: model:ir.model,name:report_qweb_signer.model_res_company |
||||
|
msgid "Companies" |
||||
|
msgstr "Compañías" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,company_id:0 |
||||
|
msgid "Company" |
||||
|
msgstr "Compañía" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,create_uid:0 |
||||
|
msgid "Created by" |
||||
|
msgstr "Creado por" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,create_date:0 |
||||
|
msgid "Created on" |
||||
|
msgstr "Creado en" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,domain:0 |
||||
|
msgid "Domain" |
||||
|
msgstr "Dominio" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,domain:0 |
||||
|
msgid "Domain for filtering if sign or not the document" |
||||
|
msgstr "Dominio para filrar si firmar o no el documento" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,attachment:0 |
||||
|
msgid "Filename used to store signed document as attachment. Keep empty to not save signed document." |
||||
|
msgstr "Nombre de fichero usado para guardar el documento firmado como adjunto. Dejar en blanco para no guardar el documento firmado." |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,id:0 |
||||
|
msgid "ID" |
||||
|
msgstr "ID" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,allow_only_one:0 |
||||
|
msgid "If True, this certificate can not be used to sign a PDF from several documents." |
||||
|
msgstr "Si está activo, este certificado no puede usarse para firmar un PDF de varios documentos." |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,write_uid:0 |
||||
|
msgid "Last Updated by" |
||||
|
msgstr "Última actualización por" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,write_date:0 |
||||
|
msgid "Last Updated on" |
||||
|
msgstr "Última actualización en" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,model_id:0 |
||||
|
msgid "Model" |
||||
|
msgstr "Modelo" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,model_id:0 |
||||
|
msgid "Model where apply this certificate" |
||||
|
msgstr "Modelo en el que usar este certificado para firmar" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,name:0 |
||||
|
msgid "Name" |
||||
|
msgstr "Nombre" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: model:ir.actions.act_window,name:report_qweb_signer.action_report_certificate |
||||
|
#: model:ir.ui.menu,name:report_qweb_signer.menu_report_certificate |
||||
|
msgid "PDF certificates" |
||||
|
msgstr "Certificados PDF" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: view:report.certificate:report_qweb_signer.view_report_certificate_form |
||||
|
msgid "PDF report certificate" |
||||
|
msgstr "Certificado de informe PDF" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: view:report.certificate:report_qweb_signer.view_report_certificate_tree |
||||
|
#: field:res.company,report_certificate_ids:0 |
||||
|
msgid "PDF report certificates" |
||||
|
msgstr "Certificados de informes PDF" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,password_file:0 |
||||
|
msgid "Password file path" |
||||
|
msgstr "Ruta al fichero de contraseña" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,path:0 |
||||
|
msgid "Path to PKCS#12 certificate file" |
||||
|
msgstr "Ruta al fichero de certificado PKCS#12" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: help:report.certificate,password_file:0 |
||||
|
msgid "Path to certificate password file" |
||||
|
msgstr "Ruta al fichero que contiene la contraseña con la que se proteje el fichero de certificado" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: code:addons/report_qweb_signer/models/report.py:77 |
||||
|
#, python-format |
||||
|
msgid "PortableSigner failed (error code: %s). Message: %s" |
||||
|
msgstr "PortableSigner falló (código de error: %s). Mensaje: %s" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: model:ir.model,name:report_qweb_signer.model_report |
||||
|
msgid "Report" |
||||
|
msgstr "Informe" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,attachment:0 |
||||
|
msgid "Save as attachment" |
||||
|
msgstr "Salvar como adjunto" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: field:report.certificate,sequence:0 |
||||
|
msgid "Sequence" |
||||
|
msgstr "Secuencia" |
||||
|
|
||||
|
#. module: report_qweb_signer |
||||
|
#: code:addons/report_qweb_signer/models/report.py:76 |
||||
|
#, python-format |
||||
|
msgid "Signing report (PDF)" |
||||
|
msgstr "Firmando informe (PDF)" |
||||
|
|
@ -0,0 +1,7 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from . import report |
||||
|
from . import report_certificate |
||||
|
from . import res_company |
@ -0,0 +1,193 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import base64 |
||||
|
from contextlib import closing |
||||
|
import os |
||||
|
import subprocess |
||||
|
import tempfile |
||||
|
import time |
||||
|
|
||||
|
from openerp import models, api, _ |
||||
|
from openerp.exceptions import Warning, AccessError |
||||
|
from openerp.tools.safe_eval import safe_eval |
||||
|
|
||||
|
import logging |
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
def _normalize_filepath(path): |
||||
|
path = path or '' |
||||
|
path = path.strip() |
||||
|
if not os.path.isabs(path): |
||||
|
me = os.path.dirname(__file__) |
||||
|
path = '{}/../static/certificate/'.format(me) + path |
||||
|
path = os.path.normpath(path) |
||||
|
return path if os.path.exists(path) else False |
||||
|
|
||||
|
|
||||
|
class Report(models.Model): |
||||
|
_inherit = 'report' |
||||
|
|
||||
|
def _certificate_get(self, cr, uid, ids, report, context=None): |
||||
|
if report.report_type != 'qweb-pdf': |
||||
|
_logger.info( |
||||
|
"Can only sign qweb-pdf reports, this one is '%s' type", |
||||
|
report.report_type) |
||||
|
return False |
||||
|
m_cert = self.pool['report.certificate'] |
||||
|
company_id = self.pool['res.users']._get_company(cr, uid) |
||||
|
certificate_ids = m_cert.search(cr, uid, [ |
||||
|
('company_id', '=', company_id), |
||||
|
('model_id', '=', report.model)], context=context) |
||||
|
if not certificate_ids: |
||||
|
_logger.info( |
||||
|
"No PDF certificate found for report '%s'", |
||||
|
report.report_name) |
||||
|
return False |
||||
|
for cert in m_cert.browse(cr, uid, certificate_ids, context=context): |
||||
|
# Check allow only one document |
||||
|
if cert.allow_only_one and len(ids) > 1: |
||||
|
_logger.info( |
||||
|
"Certificate '%s' allows only one document, " |
||||
|
"but printing %d documents", |
||||
|
cert.name, len(ids)) |
||||
|
continue |
||||
|
# Check domain |
||||
|
if cert.domain: |
||||
|
m_model = self.pool[cert.model_id.model] |
||||
|
domain = [('id', 'in', tuple(ids))] |
||||
|
domain = domain + safe_eval(cert.domain) |
||||
|
doc_ids = m_model.search(cr, uid, domain, context=context) |
||||
|
if not doc_ids: |
||||
|
_logger.info( |
||||
|
"Certificate '%s' domain not satisfied", cert.name) |
||||
|
continue |
||||
|
# Certificate match! |
||||
|
return cert |
||||
|
return False |
||||
|
|
||||
|
def _attach_filename_get(self, cr, uid, ids, certificate, context=None): |
||||
|
if len(ids) != 1: |
||||
|
return False |
||||
|
obj = self.pool[certificate.model_id.model].browse(cr, uid, ids[0]) |
||||
|
filename = safe_eval(certificate.attachment, { |
||||
|
'object': obj, |
||||
|
'time': time |
||||
|
}) |
||||
|
return filename |
||||
|
|
||||
|
def _attach_signed_read(self, cr, uid, ids, certificate, context=None): |
||||
|
if len(ids) != 1: |
||||
|
return False |
||||
|
filename = self._attach_filename_get( |
||||
|
cr, uid, ids, certificate, context=context) |
||||
|
if not filename: |
||||
|
return False |
||||
|
signed = False |
||||
|
m_attachment = self.pool['ir.attachment'] |
||||
|
attach_ids = m_attachment.search(cr, uid, [ |
||||
|
('datas_fname', '=', filename), |
||||
|
('res_model', '=', certificate.model_id.model), |
||||
|
('res_id', '=', ids[0]) |
||||
|
]) |
||||
|
if attach_ids: |
||||
|
signed = m_attachment.browse(cr, uid, attach_ids[0]).datas |
||||
|
signed = base64.decodestring(signed) |
||||
|
return signed |
||||
|
|
||||
|
def _attach_signed_write(self, cr, uid, ids, certificate, signed, |
||||
|
context=None): |
||||
|
if len(ids) != 1: |
||||
|
return False |
||||
|
filename = self._attach_filename_get( |
||||
|
cr, uid, ids, certificate, context=context) |
||||
|
if not filename: |
||||
|
return False |
||||
|
m_attachment = self.pool['ir.attachment'] |
||||
|
try: |
||||
|
attach_id = m_attachment.create(cr, uid, { |
||||
|
'name': filename, |
||||
|
'datas': base64.encodestring(signed), |
||||
|
'datas_fname': filename, |
||||
|
'res_model': certificate.model_id.model, |
||||
|
'res_id': ids[0], |
||||
|
}) |
||||
|
except AccessError: |
||||
|
raise Warning( |
||||
|
_('Saving signed report (PDF): ' |
||||
|
'You do not have enought access rights to save attachments')) |
||||
|
else: |
||||
|
_logger.info( |
||||
|
"The signed PDF document '%s' is now saved in the database", |
||||
|
filename) |
||||
|
return attach_id |
||||
|
|
||||
|
def _signer_bin(self, opts): |
||||
|
me = os.path.dirname(__file__) |
||||
|
java_bin = 'java -jar -Xms4M -Xmx4M' |
||||
|
jar = '{}/../static/jar/jPdfSign.jar'.format(me) |
||||
|
return '%s %s %s' % (java_bin, jar, opts) |
||||
|
|
||||
|
def pdf_sign(self, pdf, certificate): |
||||
|
pdfsigned = pdf + '.signed.pdf' |
||||
|
p12 = _normalize_filepath(certificate.path) |
||||
|
passwd = _normalize_filepath(certificate.password_file) |
||||
|
if not (p12 and passwd): |
||||
|
raise Warning( |
||||
|
_('Signing report (PDF): ' |
||||
|
'Certificate or password file not found')) |
||||
|
signer_opts = '"%s" "%s" "%s" "%s"' % (p12, pdf, pdfsigned, passwd) |
||||
|
signer = self._signer_bin(signer_opts) |
||||
|
process = subprocess.Popen( |
||||
|
signer, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) |
||||
|
out, err = process.communicate() |
||||
|
if process.returncode: |
||||
|
raise Warning( |
||||
|
_('Signing report (PDF): jPdfSign failed (error code: %s). ' |
||||
|
'Message: %s. Output: %s') % |
||||
|
(process.returncode, err, out)) |
||||
|
return pdfsigned |
||||
|
|
||||
|
@api.v7 |
||||
|
def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, |
||||
|
context=None): |
||||
|
signed_content = False |
||||
|
report = self._get_report_from_name(cr, uid, report_name) |
||||
|
certificate = self._certificate_get( |
||||
|
cr, uid, ids, report, context=context) |
||||
|
if certificate and certificate.attachment: |
||||
|
signed_content = self._attach_signed_read( |
||||
|
cr, uid, ids, certificate, context=context) |
||||
|
if signed_content: |
||||
|
_logger.info("The signed PDF document '%s/%s' was loaded from " |
||||
|
"the database", report_name, ids) |
||||
|
return signed_content |
||||
|
content = super(Report, self).get_pdf( |
||||
|
cr, uid, ids, report_name, html=html, data=data, |
||||
|
context=context) |
||||
|
if certificate: |
||||
|
# Creating temporary origin PDF |
||||
|
pdf_fd, pdf = tempfile.mkstemp( |
||||
|
suffix='.pdf', prefix='report.tmp.') |
||||
|
with closing(os.fdopen(pdf_fd, 'w')) as pf: |
||||
|
pf.write(content) |
||||
|
_logger.info( |
||||
|
"Signing PDF document '%s/%s' with certificate '%s'", |
||||
|
report_name, ids, certificate.name) |
||||
|
signed = self.pdf_sign(pdf, certificate) |
||||
|
# Read signed PDF |
||||
|
if os.path.exists(signed): |
||||
|
with open(signed, 'rb') as pf: |
||||
|
content = pf.read() |
||||
|
# Manual cleanup of the temporary files |
||||
|
for fname in (pdf, signed): |
||||
|
try: |
||||
|
os.unlink(fname) |
||||
|
except (OSError, IOError): |
||||
|
_logger.error('Error when trying to remove file %s', fname) |
||||
|
if certificate.attachment: |
||||
|
self._attach_signed_write( |
||||
|
cr, uid, ids, certificate, content, context=context) |
||||
|
return content |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import api, fields, models |
||||
|
|
||||
|
|
||||
|
class ReportCertificate(models.Model): |
||||
|
_name = 'report.certificate' |
||||
|
_order = 'sequence,id' |
||||
|
|
||||
|
@api.model |
||||
|
def _default_company(self): |
||||
|
m_company = self.env['res.company'] |
||||
|
return m_company._company_default_get('report.certificate') |
||||
|
|
||||
|
sequence = fields.Integer(default=10) |
||||
|
name = fields.Char(required=True) |
||||
|
path = fields.Char( |
||||
|
string="Certificate file path", required=True, |
||||
|
help="Path to PKCS#12 certificate file") |
||||
|
password_file = fields.Char( |
||||
|
string="Password file path", required=True, |
||||
|
help="Path to certificate password file") |
||||
|
model_id = fields.Many2one( |
||||
|
string="Model", required=True, |
||||
|
comodel_name='ir.model', |
||||
|
help="Model where apply this certificate") |
||||
|
domain = fields.Char( |
||||
|
string="Domain", |
||||
|
help="Domain for filtering if sign or not the document") |
||||
|
allow_only_one = fields.Boolean( |
||||
|
string="Allow only one document", default=True, |
||||
|
help="If True, this certificate can not be useb to sign " |
||||
|
"a PDF from several documents.") |
||||
|
attachment = fields.Char( |
||||
|
string="Save as attachment", |
||||
|
help="Filename used to store signed document as attachment. " |
||||
|
"Keep empty to not save signed document.") |
||||
|
company_id = fields.Many2one( |
||||
|
string='Company', comodel_name='res.company', |
||||
|
required=True, default=_default_company) |
@ -0,0 +1,14 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import models, fields |
||||
|
|
||||
|
|
||||
|
class ResCompany(models.Model): |
||||
|
_inherit = 'res.company' |
||||
|
|
||||
|
report_certificate_ids = fields.One2many( |
||||
|
string="PDF report certificates", |
||||
|
comodel_name='report.certificate', |
||||
|
inverse_name='company_id') |
@ -0,0 +1,3 @@ |
|||||
|
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
||||
|
"access_report_certificate_public","report_certificate group_public","model_report_certificate","base.group_user",1,0,0,0 |
||||
|
"access_report_certificate_manager","report_certificate group_manager","model_report_certificate","base.group_erp_manager",1,1,1,1 |
@ -0,0 +1 @@ |
|||||
|
admin |
After Width: 128 | Height: 128 | Size: 2.8 KiB |
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 125" enable-background="new 0 0 100 100" xml:space="preserve"><rect x="24.469" y="28.037" fill="#000000" width="51.159" height="5.592"/><rect x="24.469" y="16.401" fill="#000000" width="51.159" height="5.592"/><rect x="24.469" y="39.672" fill="#000000" width="51.159" height="5.592"/><rect x="24.469" y="51.309" fill="#000000" width="24.345" height="5.592"/><path fill="#000000" d="M80.018,5.03H19.982c-3.724,0-6.745,3.019-6.745,6.745v73.077c0,3.727,3.021,6.744,6.745,6.744h33.784v-4.66 H22.829c-2.485,0-4.498-2.014-4.498-4.496V14.187c0-2.483,2.013-4.496,4.498-4.496h54.342c2.483,0,4.498,2.013,4.498,4.496v68.252 c0,2.482-2.015,4.496-4.498,4.496h-1.819v4.66h4.666c3.726,0,6.746-3.019,6.746-6.744V11.775C86.764,8.049,83.742,5.03,80.018,5.03z "/><path fill="#000000" d="M75.308,69.185l5.763-2.743l-5.723-2.822l3.619-5.255l-6.369,0.417l0.51-6.362l-5.309,3.543l-2.741-5.763 l-2.823,5.722l-5.256-3.616l0.416,6.366l-6.36-0.506l3.544,5.306l-5.764,2.742l5.723,2.822l-3.619,5.256l6.367-0.415l-0.508,6.359 l5.309-3.545l2.742,5.765l2.822-5.723l5.255,3.618l-0.414-6.366l6.36,0.509L75.308,69.185z M73.956,63.635L64.892,74.36 c-0.456,0.546-1.134,0.862-1.847,0.867c-0.004,0-0.01,0-0.016,0c-0.707,0-1.379-0.308-1.843-0.838l-4.05-4.647 c-0.886-1.019-0.78-2.561,0.235-3.446c1.019-0.887,2.562-0.781,3.448,0.237l2.178,2.5l7.228-8.55 c0.868-1.03,2.411-1.162,3.441-0.289C74.696,61.065,74.826,62.605,73.956,63.635z"/><polygon fill="#000000" points="72.905,94.957 64.818,88.149 56.777,94.957 56.777,81.658 61.884,79.125 64.818,84.539 67.877,79.141 72.905,81.658 "/><text x="0" y="115" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Anton Noskov</text><text x="0" y="120" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg> |
@ -0,0 +1,333 @@ |
|||||
|
import java.io.BufferedReader; |
||||
|
import java.io.File; |
||||
|
import java.io.FileInputStream; |
||||
|
import java.io.FileNotFoundException; |
||||
|
import java.io.FileOutputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStreamReader; |
||||
|
import java.net.URL; |
||||
|
import java.net.URLDecoder; |
||||
|
import java.security.CodeSource; |
||||
|
import java.security.KeyStore; |
||||
|
import java.security.KeyStoreException; |
||||
|
import java.security.NoSuchAlgorithmException; |
||||
|
import java.security.PrivateKey; |
||||
|
import java.security.Provider; |
||||
|
import java.security.ProviderException; |
||||
|
import java.security.Security; |
||||
|
import java.security.UnrecoverableKeyException; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.security.cert.CertificateException; |
||||
|
import java.util.NoSuchElementException; |
||||
|
import java.util.ResourceBundle; |
||||
|
|
||||
|
import sun.security.pkcs11.SunPKCS11; |
||||
|
|
||||
|
import com.lowagie.text.pdf.PdfReader; |
||||
|
import com.lowagie.text.pdf.PdfSignatureAppearance; |
||||
|
import com.lowagie.text.pdf.PdfStamper; |
||||
|
|
||||
|
/* |
||||
|
import com.itextpdf.text.pdf.PdfReader; |
||||
|
import com.itextpdf.text.pdf.PdfSignatureAppearance; |
||||
|
import com.itextpdf.text.pdf.PdfStamper; |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @author Jan Peter Stotz |
||||
|
* |
||||
|
*/ |
||||
|
public class JPdfSign { |
||||
|
|
||||
|
private static PrivateKey privateKey; |
||||
|
|
||||
|
private static Certificate[] certificateChain; |
||||
|
|
||||
|
private static ResourceBundle bundle = ResourceBundle.getBundle("strings"); |
||||
|
|
||||
|
private static String PRODUCTNAME = bundle.getString("productname"); |
||||
|
|
||||
|
private static String VERSION = bundle.getString("version"); |
||||
|
|
||||
|
private static String JAR_FILENAME = bundle.getString("jar-filename"); |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
// for (int i = 0; i < args.length; i++) { |
||||
|
// System.out.println("arg[" + i + "] :" + args[i]); |
||||
|
// } |
||||
|
if (args.length < 2) |
||||
|
showUsage(); |
||||
|
|
||||
|
try { |
||||
|
|
||||
|
String pkcs12FileName = args[0].trim(); |
||||
|
String pdfInputFileName = args[1]; |
||||
|
String pdfOutputFileName = args[2]; |
||||
|
boolean usePKCS12 = !(pkcs12FileName.equals("-PKCS11")); |
||||
|
|
||||
|
String passwdfile = ""; |
||||
|
if (args.length == 4) { |
||||
|
passwdfile = args[3]; |
||||
|
} |
||||
|
// System.out.println(""); |
||||
|
// System.out.println("pdfInputFileName : " + pdfInputFileName); |
||||
|
// System.out.println("pdfOutputFileName: " + pdfOutputFileName); |
||||
|
|
||||
|
if (usePKCS12) |
||||
|
readPrivateKeyFromPKCS12(pkcs12FileName, passwdfile); |
||||
|
else |
||||
|
readPrivateKeyFromPKCS11(); |
||||
|
|
||||
|
PdfReader reader = null; |
||||
|
try { |
||||
|
reader = new PdfReader(pdfInputFileName); |
||||
|
} catch (IOException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while opening the input PDF file: \"" |
||||
|
+ pdfInputFileName + "\""); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
FileOutputStream fout = null; |
||||
|
try { |
||||
|
fout = new FileOutputStream(pdfOutputFileName); |
||||
|
} catch (FileNotFoundException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while opening the output PDF file: \"" |
||||
|
+ pdfOutputFileName + "\""); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
PdfStamper stp = null; |
||||
|
try { |
||||
|
stp = PdfStamper.createSignature(reader, fout, '\0', null, true); |
||||
|
PdfSignatureAppearance sap = stp.getSignatureAppearance(); |
||||
|
sap.setCrypto(privateKey, certificateChain, null, PdfSignatureAppearance.WINCER_SIGNED); |
||||
|
// sap.setCrypto(privateKey, certificateChain, null, null); |
||||
|
// sap.setReason("I'm the author"); |
||||
|
// sap.setLocation("Lisbon"); |
||||
|
// sap.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, |
||||
|
// null); |
||||
|
sap.setCertified(true); |
||||
|
stp.close(); |
||||
|
} catch (Exception e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while signing the PDF file:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
} catch (KeyStoreException kse) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while initializing the KeyStore instance:"); |
||||
|
kse.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void readPrivateKeyFromPKCS11() throws KeyStoreException { |
||||
|
// Initialize PKCS#11 provider from config file |
||||
|
String configFileName = getConfigFilePath("pkcs11.cfg"); |
||||
|
|
||||
|
Provider p = null; |
||||
|
try { |
||||
|
p = new SunPKCS11(configFileName); |
||||
|
Security.addProvider(p); |
||||
|
} catch (ProviderException e) { |
||||
|
System.err |
||||
|
.println("Unable to load PKCS#11 provider with config file: " |
||||
|
+ configFileName); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
String pkcs11PIN = "000000"; |
||||
|
System.out.print("Please enter the smartcard pin: "); |
||||
|
try { |
||||
|
BufferedReader in = new BufferedReader(new InputStreamReader( |
||||
|
System.in)); |
||||
|
pkcs11PIN = in.readLine(); |
||||
|
// System.out.println(pkcs11PIN); |
||||
|
// System.out.println(pkcs11PIN.length()); |
||||
|
} catch (Exception e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PIN:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
KeyStore ks = null; |
||||
|
try { |
||||
|
ks = KeyStore.getInstance("pkcs11", p); |
||||
|
ks.load(null, pkcs11PIN.toCharArray()); |
||||
|
} catch (NoSuchAlgorithmException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#11 smartcard:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (CertificateException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#11 smartcard:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (IOException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#11 smartcard:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
|
||||
|
String alias = ""; |
||||
|
try { |
||||
|
alias = (String) ks.aliases().nextElement(); |
||||
|
privateKey = (PrivateKey) ks.getKey(alias, pkcs11PIN.toCharArray()); |
||||
|
} catch (NoSuchElementException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
System.err |
||||
|
.println("The selected PKCS#12 file does not contain any private keys."); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (NoSuchAlgorithmException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (UnrecoverableKeyException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
certificateChain = ks.getCertificateChain(alias); |
||||
|
} |
||||
|
|
||||
|
protected static void readPrivateKeyFromPKCS12(String pkcs12FileName, String pwdFile) |
||||
|
throws KeyStoreException { |
||||
|
String pkcs12Password = ""; |
||||
|
KeyStore ks = null; |
||||
|
if (!pwdFile.equals("")) { |
||||
|
try { |
||||
|
FileInputStream pwdfis = new FileInputStream(pwdFile); |
||||
|
byte[] pwd = new byte[1024]; |
||||
|
try { |
||||
|
do { |
||||
|
int r = pwdfis.read(pwd); |
||||
|
if (r < 0) { |
||||
|
break; |
||||
|
} |
||||
|
pkcs12Password += new String(pwd); |
||||
|
pkcs12Password = pkcs12Password.trim(); |
||||
|
} while (pwdfis.available() > 0); |
||||
|
pwdfis.close(); |
||||
|
} catch (IOException ex) { |
||||
|
System.err |
||||
|
.println("Can't read password file: " + pwdFile); |
||||
|
} |
||||
|
} catch (FileNotFoundException fnfex) { |
||||
|
System.err |
||||
|
.println("Password file not found: " + pwdFile); |
||||
|
} |
||||
|
} else { |
||||
|
System.out.print("Please enter the password for \"" + pkcs12FileName |
||||
|
+ "\": "); |
||||
|
try { |
||||
|
BufferedReader in = new BufferedReader(new InputStreamReader( |
||||
|
System.in)); |
||||
|
pkcs12Password = in.readLine(); |
||||
|
} catch (Exception e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the password:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
ks = KeyStore.getInstance("pkcs12"); |
||||
|
ks.load(new FileInputStream(pkcs12FileName), pkcs12Password |
||||
|
.toCharArray()); |
||||
|
} catch (NoSuchAlgorithmException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#12 file:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (CertificateException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#12 file:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (FileNotFoundException e) { |
||||
|
System.err.println("Unable to open the PKCS#12 keystore file \"" |
||||
|
+ pkcs12FileName + "\":"); |
||||
|
System.err |
||||
|
.println("The file does not exists or missing read permission."); |
||||
|
System.exit(-1); |
||||
|
} catch (IOException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while reading the PKCS#12 file:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
String alias = ""; |
||||
|
try { |
||||
|
alias = (String) ks.aliases().nextElement(); |
||||
|
privateKey = (PrivateKey) ks.getKey(alias, pkcs12Password |
||||
|
.toCharArray()); |
||||
|
} catch (NoSuchElementException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
System.err |
||||
|
.println("The selected PKCS#12 file does not contain any private keys."); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (NoSuchAlgorithmException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} catch (UnrecoverableKeyException e) { |
||||
|
System.err |
||||
|
.println("An unknown error accoured while retrieving the private key:"); |
||||
|
e.printStackTrace(); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
certificateChain = ks.getCertificateChain(alias); |
||||
|
} |
||||
|
|
||||
|
protected static String getConfigFilePath(String configFilename) { |
||||
|
CodeSource source = JPdfSign.class.getProtectionDomain() |
||||
|
.getCodeSource(); |
||||
|
URL url = source.getLocation(); |
||||
|
|
||||
|
String jarPath = URLDecoder.decode(url.getFile()); |
||||
|
File f = new File(jarPath); |
||||
|
try { |
||||
|
jarPath = f.getCanonicalPath(); |
||||
|
} catch (IOException e) { |
||||
|
} |
||||
|
if (!f.isDirectory()) { |
||||
|
f = new File(jarPath); |
||||
|
jarPath = f.getParent(); |
||||
|
} |
||||
|
System.out.println(jarPath); |
||||
|
if (jarPath.length() > 0) { |
||||
|
return jarPath + File.separator + configFilename; |
||||
|
} else |
||||
|
return configFilename; |
||||
|
} |
||||
|
|
||||
|
public static void showUsage() { |
||||
|
System.out.println("jPdfSign v" + VERSION |
||||
|
+ " by Jan Peter Stotz - jpstotz@gmx.de\n"); |
||||
|
System.out.println(PRODUCTNAME + " usage:"); |
||||
|
System.out |
||||
|
.println("\nFor using a PKCS#12 (.p12) file as signature certificate and private key source:"); |
||||
|
System.out.print("\tjava -jar " + JAR_FILENAME); |
||||
|
System.out |
||||
|
.println(" pkcs12FileName pdfInputFileName pdfOutputFileName"); |
||||
|
System.out |
||||
|
.println("\nFor using a PKCS#11 smartcard as signature certificate and private key source:"); |
||||
|
System.out.print("\tjava -jar" + JAR_FILENAME); |
||||
|
System.out.println(" -PKCS11 pdfInputFileName pdfOutputFileName"); |
||||
|
System.exit(0); |
||||
|
} |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
productname=jPdfSign |
||||
|
version=0.3.1 |
||||
|
jar-filename=jPdfSign.jar |
@ -0,0 +1,66 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
© 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record id="view_report_certificate_form" model="ir.ui.view"> |
||||
|
<field name="name">report.certificate.form</field> |
||||
|
<field name="model">report.certificate</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="PDF report certificate"> |
||||
|
<sheet string="PDF report certificate"> |
||||
|
<div class="oe_title"> |
||||
|
<label for="name" class="oe_edit_only"/> |
||||
|
<h1><field name="name"/></h1> |
||||
|
</div> |
||||
|
<group> |
||||
|
<group> |
||||
|
<field name="path"/> |
||||
|
<field name="password_file"/> |
||||
|
<field name="model_id"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="domain"/> |
||||
|
<field name="allow_only_one"/> |
||||
|
<field name="attachment"/> |
||||
|
<field name="company_id" widget="selection" |
||||
|
groups="base.group_multi_company"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_report_certificate_tree" model="ir.ui.view" > |
||||
|
<field name="name">report.certificate.tree</field> |
||||
|
<field name="model">report.certificate</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="PDF report certificates"> |
||||
|
<field name="sequence" widget="handle"/> |
||||
|
<field name="name"/> |
||||
|
<field name="path"/> |
||||
|
<field name="model_id"/> |
||||
|
<field name="domain"/> |
||||
|
<field name="company_id"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_report_certificate" model="ir.actions.act_window"> |
||||
|
<field name="name">PDF certificates</field> |
||||
|
<field name="res_model">report.certificate</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="menu_report_certificate" |
||||
|
name="PDF certificates" |
||||
|
parent="report.reporting_menuitem" |
||||
|
action="action_report_certificate"/> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,26 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
© 2015 Antiun Ingenieria S.L. - Antonio Espinosa |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record id="view_company_form" model="ir.ui.view"> |
||||
|
<field name="name">Add PDF report certificates list</field> |
||||
|
<field name="inherit_id" ref="base.view_company_form" /> |
||||
|
<field name="model">res.company</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<data> |
||||
|
<xpath expr="//page[@string='Report Configuration']/group[@string='Configuration']" position="after"> |
||||
|
<group string="Certificates" col="2"> |
||||
|
<field name="report_certificate_ids" |
||||
|
context="{'default_company_id': active_id}"/> |
||||
|
</group> |
||||
|
</xpath> |
||||
|
</data> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue