diff --git a/.copier-answers.yml b/.copier-answers.yml index 8ee4b01af4..bb275c6d83 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.29 +_commit: v1.35 _src_path: git+https://github.com/OCA/oca-addons-repo-template additional_ruff_rules: [] ci: GitHub @@ -18,7 +18,7 @@ odoo_version: 18.0 org_name: Odoo Community Association (OCA) org_slug: OCA rebel_module_groups: - - password_security +- password_security repo_description: server-auth repo_name: server-auth repo_slug: server-auth diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..e0d56685a9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +test-requirements.txt merge=union diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9d5e162c3..5851b71598 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,7 @@ jobs: makepot: "true" services: postgres: - image: postgres:12.0 + image: postgres:12 env: POSTGRES_USER: odoo POSTGRES_PASSWORD: odoo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4aa74dde7..d077843281 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,11 +40,11 @@ repos: language: fail files: '[a-zA-Z0-9_]*/i18n/en\.po$' - repo: https://github.com/sbidoul/whool - rev: v1.2 + rev: v1.3 hooks: - id: whool-init - repo: https://github.com/oca/maintainer-tools - rev: bf9ecb9938b6a5deca0ff3d870fbd3f33341fded + rev: b89f767503be6ab2b11e4f50a7557cb20066e667 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -96,6 +96,7 @@ repos: additional_dependencies: - "eslint@9.12.0" - "eslint-plugin-jsdoc@50.3.1" + - "globals@16.0.0" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: diff --git a/.pylintrc b/.pylintrc index 7c62b6d2ed..d103ffcd9a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -25,19 +25,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -48,56 +54,50 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, + missing-newline-extrafiles, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + str-format-used, + unnecessary-utf8-coding-comment, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + attribute-string-redundant, + consider-merging-classes-inherited, + context-overridden, + except-pass, invalid-commit, manifest-maintainers-list, - missing-newline-extrafiles, missing-readme, missing-return, odoo-addons-relative-import, - old-api7-method-defined, - po-msgstr-variables, - po-syntax-error, renamed-field-parameter, resource-not-exist, - str-format-used, test-folder-imported, translation-contains-variable, translation-positional-used, - unnecessary-utf8-coding-comment, website-manifest-key-not-valid-uri, - xml-attribute-translatable, - xml-deprecated-qweb-directive, - xml-deprecated-tree-attribute, external-request-timeout, # messages that do not cause the lint step to fail consider-merging-classes-inherited, @@ -114,7 +114,8 @@ enable=anomalous-backslash-in-string, old-api7-method-defined, redefined-builtin, too-complex, - unnecessary-utf8-coding-comment + unnecessary-utf8-coding-comment, + manifest-external-assets [REPORTS] diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index 018fd61cdd..73674c04d4 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -17,19 +17,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -40,56 +46,50 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, + missing-newline-extrafiles, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + str-format-used, + unnecessary-utf8-coding-comment, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + attribute-string-redundant, + consider-merging-classes-inherited, + context-overridden, + except-pass, invalid-commit, manifest-maintainers-list, - missing-newline-extrafiles, missing-readme, missing-return, odoo-addons-relative-import, - old-api7-method-defined, - po-msgstr-variables, - po-syntax-error, renamed-field-parameter, resource-not-exist, - str-format-used, test-folder-imported, translation-contains-variable, translation-positional-used, - unnecessary-utf8-coding-comment, website-manifest-key-not-valid-uri, - xml-attribute-translatable, - xml-deprecated-qweb-directive, - xml-deprecated-tree-attribute, external-request-timeout [REPORTS] diff --git a/README.md b/README.md index 403dcf4bb5..c3ed52d72d 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,12 @@ addon | version | maintainers | summary [auth_user_case_insensitive](auth_user_case_insensitive/) | 18.0.1.0.0 | | Makes the user login field case insensitive [base_user_empty_password](base_user_empty_password/) | 18.0.1.0.0 | grindtildeath | Allows to empty password of users [base_user_show_email](base_user_show_email/) | 18.0.1.0.0 | | Untangle user login and email -[impersonate_login](impersonate_login/) | 18.0.1.0.0 | Kev-Roche | tools +[impersonate_login](impersonate_login/) | 18.0.1.1.0 | Kev-Roche | tools [password_security](password_security/) | 18.0.1.0.0 | | Allow admin to set password security requirements. [user_log_view](user_log_view/) | 18.0.1.0.0 | trojikman | Allow to see user's actions log [users_ldap_mail](users_ldap_mail/) | 18.0.1.0.0 | joao-p-marques | LDAP mapping for user name and e-mail +[vault](vault/) | 18.0.1.0.1 | | Password vault integration in Odoo +[vault_share](vault_share/) | 18.0.1.0.0 | | Implementation of a mechanism to share secrets [//]: # (end addons) diff --git a/eslint.config.cjs b/eslint.config.cjs index 0d5731f89a..dd0cbe0aef 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -1,3 +1,4 @@ +var globals = require('globals'); jsdoc = require("eslint-plugin-jsdoc"); const config = [{ @@ -16,6 +17,8 @@ const config = [{ openerp: "readonly", owl: "readonly", luxon: "readonly", + QUnit: "readonly", + ...globals.browser, }, ecmaVersion: 2024, @@ -191,7 +194,7 @@ const config = [{ }, }, { - files: ["**/*.esm.js"], + files: ["**/*.esm.js", "**/*test.js"], languageOptions: { ecmaVersion: 2024, diff --git a/impersonate_login/README.rst b/impersonate_login/README.rst index 66afba1389..2b71b498b5 100644 --- a/impersonate_login/README.rst +++ b/impersonate_login/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================= Impersonate Login ================= @@ -7,13 +11,13 @@ Impersonate Login !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:1c22b5e85d268d1f7b8b3d64d9d2d2acb8b05a9cb3170a33fd4dcdc64fbdbd61 + !! source digest: sha256:0f4564be316d51d716922597d0fbfc4ba6ee6b58b19243f17fc445dd6d9d3a4c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github diff --git a/impersonate_login/__manifest__.py b/impersonate_login/__manifest__.py index e07a936e97..e7a9ef963f 100644 --- a/impersonate_login/__manifest__.py +++ b/impersonate_login/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Impersonate Login", "summary": "tools", - "version": "18.0.1.0.0", + "version": "18.0.1.1.0", "category": "Tools", "website": "https://github.com/OCA/server-auth", "author": "Akretion, Odoo Community Association (OCA)", diff --git a/impersonate_login/i18n/es.po b/impersonate_login/i18n/es.po new file mode 100644 index 0000000000..87a8e0443b --- /dev/null +++ b/impersonate_login/i18n/es.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * impersonate_login +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-12-26 17:42+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: impersonate_login +#. odoo-javascript +#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0 +msgid "Back to Original User" +msgstr "Volver al usuario original" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_base +msgid "Base" +msgstr "Base" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__body +#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__body +msgid "Contents" +msgstr "Contenidos" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_end +msgid "End Date" +msgstr "Fecha de fin" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_ir_http +msgid "HTTP Routing" +msgstr "Ruta HTTP" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__id +msgid "ID" +msgstr "ID" + +#. module: impersonate_login +#: model:ir.actions.act_window,name:impersonate_login.impersonate_log_action +msgid "Impersonate Login Logs" +msgstr "Registros de inicio de sesión suplantado" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_impersonate_log +msgid "Impersonate Logs" +msgstr "Registros de suplantación de identidad" + +#. module: impersonate_login +#: model:res.groups,name:impersonate_login.group_impersonate_login +msgid "Impersonate Users" +msgstr "Usuarios suplantados" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__impersonated_author_id +#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__impersonated_author_id +msgid "Impersonated Author" +msgstr "Autor suplantado" + +#. module: impersonate_login +#: model:ir.ui.menu,name:impersonate_login.menu_impersonate_log +msgid "Impersonated Logs" +msgstr "Registros suplantados" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/res_users.py:0 +msgid "It's you." +msgstr "Eres tú." + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__impersonated_partner_id +msgid "Logged as" +msgstr "Iniciado sesión como" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/mail_message.py:0 +msgid "Logged in as {}" +msgstr "Iniciado sesión como {}" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_mail_message +msgid "Message" +msgstr "Mensaje" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_start +msgid "Start Date" +msgstr "Fecha de inicio" + +#. module: impersonate_login +#. odoo-javascript +#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0 +#: model_terms:ir.ui.view,arch_db:impersonate_login.impersonate_res_users_tree +msgid "Switch Login" +msgstr "Cambiar de inicio de sesión" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_res_users +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__user_id +msgid "User" +msgstr "Usuario" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/res_users.py:0 +msgid "You are already Logged as another user." +msgstr "Ya has iniciado sesión como otro usuario." diff --git a/impersonate_login/i18n/es_VE.po b/impersonate_login/i18n/es_VE.po new file mode 100644 index 0000000000..45a6223de8 --- /dev/null +++ b/impersonate_login/i18n/es_VE.po @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * impersonate_login +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-12-26 17:42+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: impersonate_login +#. odoo-javascript +#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0 +msgid "Back to Original User" +msgstr "Volver al usuario original" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_base +msgid "Base" +msgstr "Base" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__body +#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__body +msgid "Contents" +msgstr "Contenidos" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_end +msgid "End Date" +msgstr "Fecha de fin" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_ir_http +msgid "HTTP Routing" +msgstr "Ruta HTTP" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__id +msgid "ID" +msgstr "ID" + +#. module: impersonate_login +#: model:ir.actions.act_window,name:impersonate_login.impersonate_log_action +msgid "Impersonate Login Logs" +msgstr "Registros de inicio de sesión suplantado" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_impersonate_log +msgid "Impersonate Logs" +msgstr "Registros de suplantación de identidad" + +#. module: impersonate_login +#: model:res.groups,name:impersonate_login.group_impersonate_login +msgid "Impersonate Users" +msgstr "Usuarios suplantados" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__impersonated_author_id +#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__impersonated_author_id +msgid "Impersonated Author" +msgstr "Autor suplantado" + +#. module: impersonate_login +#: model:ir.ui.menu,name:impersonate_login.menu_impersonate_log +msgid "Impersonated Logs" +msgstr "Registros suplantados" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/res_users.py:0 +msgid "It's you." +msgstr "Eres tú." + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__impersonated_partner_id +msgid "Logged as" +msgstr "Iniciado sesión como" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/mail_message.py:0 +msgid "Logged in as {}" +msgstr "Iniciado sesión como {}" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_mail_message +msgid "Message" +msgstr "Mensaje" + +#. module: impersonate_login +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_start +msgid "Start Date" +msgstr "Fecha de inicio" + +#. module: impersonate_login +#. odoo-javascript +#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0 +#: model_terms:ir.ui.view,arch_db:impersonate_login.impersonate_res_users_tree +msgid "Switch Login" +msgstr "Cambiar de inicio de sesión" + +#. module: impersonate_login +#: model:ir.model,name:impersonate_login.model_res_users +#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__user_id +msgid "User" +msgstr "Usuario" + +#. module: impersonate_login +#. odoo-python +#: code:addons/impersonate_login/models/res_users.py:0 +msgid "You are already Logged as another user." +msgstr "Ya has iniciado sesión como otro usuario." diff --git a/impersonate_login/models/mail_message.py b/impersonate_login/models/mail_message.py index e7bf2fd4dc..6227265ed9 100644 --- a/impersonate_login/models/mail_message.py +++ b/impersonate_login/models/mail_message.py @@ -14,6 +14,7 @@ class Message(models.Model): comodel_name="res.partner", compute="_compute_impersonated_author_id", store=True, + index=True, ) body = fields.Html( diff --git a/impersonate_login/static/description/index.html b/impersonate_login/static/description/index.html index 3aa102e36d..1cf66496f8 100644 --- a/impersonate_login/static/description/index.html +++ b/impersonate_login/static/description/index.html @@ -3,7 +3,7 @@ -Impersonate Login +README.rst -
-

Impersonate Login

+
+ + +Odoo Community Association + +
+

Impersonate Login

-

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module allows one user (for example, a member of the support team) to log in as another user. The impersonation session can be exited by clicking on the button “Back to Original User”.

@@ -400,11 +405,11 @@

Impersonate Login

-

Configuration

+

Configuration

The impersonating user must belong to group “Impersonate Users”.

-

Usage

+

Usage

  1. In the menu that is displayed when clicking on the user avatar on the top right corner, or in the res.users list, click “Switch Login” to @@ -414,7 +419,7 @@

    Usage

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -422,15 +427,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Akretion
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -455,5 +460,6 @@

Maintainers

+
diff --git a/impersonate_login/static/src/js/user_menu.esm.js b/impersonate_login/static/src/js/user_menu.esm.js index b467c47f17..de54130e77 100644 --- a/impersonate_login/static/src/js/user_menu.esm.js +++ b/impersonate_login/static/src/js/user_menu.esm.js @@ -1,4 +1,3 @@ -/** @odoo-module **/ // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import {_t} from "@web/core/l10n/translation"; diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 5f00e35f09..815ed04f61 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-server-auth" -version = "18.0.20251029.0" +version = "18.0.20251217.0" dependencies = [ "odoo-addon-auth_admin_passkey==18.0.*", "odoo-addon-auth_api_key==18.0.*", @@ -22,6 +22,8 @@ dependencies = [ "odoo-addon-password_security==18.0.*", "odoo-addon-user_log_view==18.0.*", "odoo-addon-users_ldap_mail==18.0.*", + "odoo-addon-vault==18.0.*", + "odoo-addon-vault_share==18.0.*", ] classifiers=[ "Programming Language :: Python", diff --git a/vault/README.rst b/vault/README.rst new file mode 100644 index 0000000000..482e293d58 --- /dev/null +++ b/vault/README.rst @@ -0,0 +1,122 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +===== +Vault +===== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:4f91a8f4641c642226369994ad7a6532d1600c33246c36bf698c4fc846ba18d0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github + :target: https://github.com/OCA/server-auth/tree/18.0/vault + :alt: OCA/server-auth +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-vault + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module implements a vault for secrets and files using +end-to-end-encryption. The encryption and decryption happens in the +browser using a vault specific shared master key. The master keys are +encrypted using asymmetrically. For this the user has to enter a second +password on the first login or if he needs to access data in a vault. +The asymmetric keys are stored for a certain time in the browser +storage. + +The server can never access the secrets with the information available. +Only people registered in the vault can decrypt or encrypt values in a +vault. The meta data isn't encrypted to be able to search/filter for +entries more easily. + +This modules requires a secure context for the browser to work properly +and therefore HTTPS support is required. + +The `vault-recovery `__ +project focuses on disaster recovery in case of an incident to recover +secrets from old database backups or old exports. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +- Field and file history for restoration +- Import improvement + +.. + + - Support challenge-response/FIDO2 + - Support for argon2 and kdbx v4 + +- When changing an entry from one vault to another existing vault, the + values added on this entry cannot be accessed, so the field vault is + going to be readonly when it is defined. + + If you want to move entries between vaults you can use the export -> + import option. + +- HTTPS or localhost (secure browser context) is required for the client + side encryption + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* initOS GmbH + +Contributors +------------ + +- Florian Kantelberg +- `Tecnativa `__: + + - Carlos Roca + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/vault/TECHNICAL.rst b/vault/TECHNICAL.rst new file mode 100644 index 0000000000..4a5456445d --- /dev/null +++ b/vault/TECHNICAL.rst @@ -0,0 +1,143 @@ +:: + + ┌───────┐ ┏━━━━━━━━━━━━━┓ ╔═══════════╗ + │ input │ ┃ unencrypted ┃ ║ encrypted ║ + └───────┘ ┗━━━━━━━━━━━━━┛ ╚═══════════╝ + +Vault +===== + +Each vault stores entries with enrypted fields and files in a tree like structure. The access is controlled per vault. Every added user can read the secrets of a vault. Otherwise the users can receive permission to share the vault with other users, to write secrets in the vault, or to delete entries of the vault. The databases stores the public and password protected private key of each user. The password used for the private key is derived from a password entered by the user and should be different than the password used for the login. Keep in mind that the meta information like field name or file names aren't encrypted. + +Shared-key encryption +===================== + +To be able to securely share sensitive data between all users a shared-key encryption is used. All users share a common secret for each vault. This secret is encrypted by the public key of each user to grant access to the user by using the private key to restore the secret. + +Encryption of master key +------------------------ + +:: + + . ┏━━━━━━━━━━━━┓ + ┃ Master key ┃ + ┗━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━┓ ┃ + ┃ User ┃ ▼ + ┃ ┃ ┏━━━━━━━━━┓ + ┃ ┏━━━━━━━━━━━━━┓ ┃ ┃ encrypt ┃ ╔════════════╗ + ┃ ┃ Public key ┃━━━━▶┃ (RSA) ┃━━━━━▶║ Master key ║ + ┃ ┗━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━┛ ╚════════════╝ + ┃ ╔═════════════╗ ┃ + ┃ ║ Private key ║ ┃ + ┃ ╚═════════════╝ ┃ + ┗━━━━━━━━━━━━━━━━━┛ + +Decryption of master key +------------------------ + +:: + + . ┌──────────┐ ┏━━━━━━━━━━┓ + │ Password │━━━━▶┃ derive ┃ + └──────────┘ ┃ (PBKDF2) ┃ + ┗━━━━━━━━━━┛ + ┃ + ┏━━━━━━━━━━━━━━━━━┓ ▼ ╔════════════╗ + ┃ User ┃ ┏━━━━━━━━━━┓ ║ Master key ║ + ┃ ┃ ┃ Password ┃ ╚════════════╝ + ┃ ┏━━━━━━━━━━━━━┓ ┃ ┗━━━━━━━━━━┛ ┃ + ┃ ┃ Public key ┃ ┃ ┃ ▼ + ┃ ┗━━━━━━━━━━━━━┛ ┃ ▼ ┏━━━━━━━━━┓ + ┃ ╔═════════════╗ ┃ ┏━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┃ decrypt ┃ ┏━━━━━━━━━━━━┓ + ┃ ║ Private key ║━━━━━┃ unlock ┃━━▶┃ Private key ┃━━━▶┃ (RSA) ┃━━━━━▶┃ Master key ┃ + ┃ ╚═════════════╝ ┃ ┗━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ + ┗━━━━━━━━━━━━━━━━━┛ + +Symmetric encryption of the data +================================ + +The symmetric cipher AES is used with the common master key to encrypt/decrypt the secrets of the vaults. The encryption parameter and encrypted data is stored in the database while everything else happens in the browser. + +Encryption of data +------------------ + +:: + + . ┏━━━━━━━━━━━━┓ + ┃ Master key ┃ + ┗━━━━━━━━━━━━┛ + ┃ ┏━━━━━━━━━━━━━━━━━━┓ + ▼ ┃ Database ┃ + ┏━━━━━━━━━┓ ┃ ┃ + ┏━━━━━━━━━━━━┓ ┃ encrypt ┃ ┃╔════════════════╗┃ + ┃ Plain text ┃━━▶┃ (AES) ┃━━━▶║ Encrypted data ║┃ + ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃╚════════════════╝┃ + ┃ ┃┏━━━━━━━━━━━━━━━━┓┃ + ┗━━━━━━━━▶┃ Parameters ┃┃ + ┃┗━━━━━━━━━━━━━━━━┛┃ + ┗━━━━━━━━━━━━━━━━━━┛ + +Decryption of data +------------------ + +:: + + . ┏━━━━━━━━━━━━┓ + ┃ Master key ┃ + ┗━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━━┓ ┃ + ┃ Database ┃ ▼ + ┃ ┃ ┏━━━━━━━━━┓ + ┃╔════════════════╗┃ ┃ decrypt ┃ ┏━━━━━━━━━━━━┓ + ┃║ Encrypted data ║━━━▶┃ (AES) ┃━━▶┃ Plain text ┃ + ┃╚════════════════╝┃ ┗━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ + ┃┏━━━━━━━━━━━━━━━━┓┃ ▲ + ┃┃ Parameters ┃━━━━━━━━┛ + ┃┗━━━━━━━━━━━━━━━━┛┃ + ┗━━━━━━━━━━━━━━━━━━┛ + +Inbox +===== + +This allows an user to receive encrypted secrets by external or internal Odoo users. External users have to use either the owner specific inbox link from his preferences or the link of an already created inbox. The value is symmetrically encrypted. The key for the encryption is wrapped with the public key of the user of the inbox to grant the user the access to the key. Internal users can directly send a secret from a vault entry to another user who has enabled this feature. If a direct link is used the access counter and expiration time can block an overwrite. + +Encryption of inbox +------------------- + +:: + + . ┏━━━━━━━━━━━━┓ + ┃ Plain data ┃ + ┗━━━━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━━━━┓ ┃ + ┃ User ┃ ▼ + ┃ ┃ ┏━━━━━━━━━┓ + ┃ ┏━━━━━━━━━━━━━┓ ┃ ┃ encrypt ┃ ╔════════════════╗ + ┃ ┃ Public key ┃━━━━▶┃ (RSA) ┃━━━━━▶║ Encrypted data ║ + ┃ ┗━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━┛ ╚════════════════╝ + ┃ ╔═════════════╗ ┃ + ┃ ║ Private key ║ ┃ + ┃ ╚═════════════╝ ┃ + ┗━━━━━━━━━━━━━━━━━┛ + +Decryption of inbox +------------------- + +:: + + . ┌──────────┐ ┏━━━━━━━━━━┓ + │ Password │━━━━▶┃ derive ┃ + └──────────┘ ┃ (PBKDF2) ┃ + ┗━━━━━━━━━━┛ + ┃ + ┏━━━━━━━━━━━━━━━━━┓ ▼ ╔════════════════╗ + ┃ User ┃ ┏━━━━━━━━━━┓ ║ Encrypted data ║ + ┃ ┃ ┃ Password ┃ ╚════════════════╝ + ┃ ┏━━━━━━━━━━━━━┓ ┃ ┗━━━━━━━━━━┛ ┃ + ┃ ┃ Public key ┃ ┃ ┃ ▼ + ┃ ┗━━━━━━━━━━━━━┛ ┃ ▼ ┏━━━━━━━━━┓ + ┃ ╔═════════════╗ ┃ ┏━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┃ decrypt ┃ ┏━━━━━━━━━━━━┓ + ┃ ║ Private key ║━━━━━┃ unlock ┃━━▶┃ Private key ┃━━━▶┃ (RSA) ┃━━━━━▶┃ Plain data ┃ + ┃ ╚═════════════╝ ┃ ┗━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ + ┗━━━━━━━━━━━━━━━━━┛ diff --git a/vault/__init__.py b/vault/__init__.py new file mode 100644 index 0000000000..843ac90a95 --- /dev/null +++ b/vault/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import controllers, models, wizards diff --git a/vault/__manifest__.py b/vault/__manifest__.py new file mode 100644 index 0000000000..8f8f90d36d --- /dev/null +++ b/vault/__manifest__.py @@ -0,0 +1,50 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Vault", + "summary": "Password vault integration in Odoo", + "license": "AGPL-3", + "version": "18.0.1.0.1", + "website": "https://github.com/OCA/server-auth", + "application": True, + "author": "initOS GmbH, Odoo Community Association (OCA)", + "category": "Vault", + "depends": ["base_setup", "web"], + "data": [ + "security/ir.model.access.csv", + "security/ir_rule.xml", + "security/vault_security.xml", + "views/res_config_settings_views.xml", + "views/res_users_views.xml", + "views/vault_entry_views.xml", + "views/vault_field_views.xml", + "views/vault_file_views.xml", + "views/vault_log_views.xml", + "views/vault_inbox_views.xml", + "views/vault_right_views.xml", + "views/vault_views.xml", + "views/menuitems.xml", + "views/templates.xml", + "wizards/vault_export_wizard.xml", + "wizards/vault_import_wizard.xml", + "wizards/vault_send_wizard.xml", + "wizards/vault_store_wizard.xml", + ], + "assets": { + "vault.assets_frontend": [ + "vault/static/src/common/*.js", + "vault/static/src/frontend/*.js", + ], + "web.assets_backend": [ + "vault/static/lib/**/*.min.js", + "vault/static/src/**/*.xml", + "vault/static/src/common/*.js", + "vault/static/src/backend/*.scss", + "vault/static/src/backend/**/*.js", + ], + "web.tests_assets": [ + "vault/static/tests/**/*.js", + ], + }, +} diff --git a/vault/controllers/__init__.py b/vault/controllers/__init__.py new file mode 100644 index 0000000000..aabfa83edd --- /dev/null +++ b/vault/controllers/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import main diff --git a/vault/controllers/main.py b/vault/controllers/main.py new file mode 100644 index 0000000000..1ca361dda3 --- /dev/null +++ b/vault/controllers/main.py @@ -0,0 +1,152 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, http +from odoo.exceptions import AccessDenied +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class Controller(http.Controller): + @http.route("/vault/inbox/", type="http", auth="public") + def vault_inbox(self, token): + ctx = {"disable_footer": True, "token": token} + # Find the right token + inbox = request.env["vault.inbox"].sudo().find_inbox(token) + user = request.env["res.users"].sudo().find_user_of_inbox(token) + if len(inbox) == 1 and inbox.accesses > 0: + ctx.update({"name": inbox.name, "public": inbox.user_id.active_key.public}) + elif len(inbox) == 0 and len(user) == 1: + ctx["public"] = user.active_key.public + + # A valid token would mean we found a public key + if not ctx.get("public"): + ctx["error"] = _("Invalid token") + return request.render("vault.inbox", ctx) + + # Just render if GET method + if request.httprequest.method != "POST": + return request.render("vault.inbox", ctx) + + # Check the param + name = request.params.get("name") + secret = request.params.get("encrypted") + secret_file = request.params.get("encrypted_file") + filename = request.params.get("filename") + iv = request.params.get("iv") + key = request.params.get("key") + if not name: + ctx["error"] = _("Please specify a name") + return request.render("vault.inbox", ctx) + + if not secret and not secret_file: + ctx["error"] = _("No secret found") + return request.render("vault.inbox", ctx) + + if secret_file and not filename: + ctx["error"] = _("Missing filename") + return request.render("vault.inbox", ctx) + + if not iv or not key: + ctx["error"] = _("Something went wrong with the encryption") + return request.render("vault.inbox", ctx) + + try: + inbox.store_in_inbox( + name, + secret, + secret_file, + iv, + key, + user, + filename, + ip=request.httprequest.remote_addr, + ) + except Exception as e: + _logger.exception(e) + ctx["error"] = _( + "An error occured. Please contact the user or administrator" + ) + return request.render("vault.inbox", ctx) + + ctx["message"] = _("Successfully stored") + return request.render("vault.inbox", ctx) + + @http.route("/vault/public", type="json") + def vault_public(self, user_id): + """Get the public key of a specific user""" + user = request.env["res.users"].sudo().browse(user_id).exists() + if not user or not user.keys: + return {} + + return {"public_key": user.active_key.public} + + @http.route("/vault/inbox/get", auth="user", type="json") + def vault_get_inbox(self): + inboxes = request.env.user.inbox_ids + return {inbox.token: inbox.key for inbox in inboxes} + + @http.route("/vault/inbox/store", auth="user", type="json") + def vault_store_inbox(self, keys): + if not isinstance(keys, dict): + return + + for inbox in request.env.user.inbox_ids: + key = keys.get(inbox.token) + + if isinstance(key, str): + inbox.key = key + + @http.route("/vault/keys/store", auth="user", type="json") + def vault_store_keys(self, **kwargs): + """Store the key pair for the current user""" + return request.env["res.users.key"].store(**kwargs) + + @http.route("/vault/keys/get", auth="user", type="json") + def vault_get_keys(self): + """Get the currently active key pair""" + return request.env.user.get_vault_keys() + + @http.route("/vault/rights/get", auth="user", type="json") + def vault_get_right_keys(self): + """Get the master keys from the vault.right records""" + rights = request.env.user.vault_right_ids + return {right.vault_id.uuid: right.key for right in rights} + + @http.route("/vault/rights/store", auth="user", type="json") + def vault_store_right_keys(self, keys): + """Store the master keys to the specific vault.right records""" + if not isinstance(keys, dict): + return + + for right in request.env.user.vault_right_ids: + master_key = keys.get(right.vault_id.uuid) + + if isinstance(master_key, str): + right.sudo().key = master_key + + @http.route("/vault/replace", auth="user", type="json") + def vault_replace(self, data): + """Replace the master keys and values within a single transaction""" + if not isinstance(data, list): + return + + vault = request.env["vault"].with_context(vault_skip_log=True) + for changes in data: + record = vault.env[changes["model"]].browse(changes["id"]) + if not record.vault_id.allowed_write: + raise AccessDenied() + + vault |= record.vault_id + if record._name in ("vault.field", "vault.file"): + record.write({k: v for k, v in changes.items() if k in ["iv", "value"]}) + elif record._name == "vault.right": + record.write({k: v for k, v in changes.items() if k in ["key"]}) + + for v in vault: + v._log_entry("Replaced the keys", "info") + + vault.sudo().write({"reencrypt_required": False}) diff --git a/vault/i18n/es.po b/vault/i18n/es.po new file mode 100644 index 0000000000..eb66e65919 --- /dev/null +++ b/vault/i18n/es.po @@ -0,0 +1,1507 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-06-06 06:57+0000\n" +"PO-Revision-Date: 2024-04-16 10:25+0000\n" +"Last-Translator: Carlos Roca Zaragoza \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s entrada %(name)s por %(user)s" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "%s '%s' of entry '%s'" +msgstr "%s '%s' del registro '%s'" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%s (copy)" +msgstr "%s (copia)" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "" +"A secure browser context is required. Please switch to https or contact your " +"administrator" +msgstr "" +"Se requiere un contexto de navegador seguro. Por favor, cambie a https o " +"contacte a su administrador" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault_field.py:0 +#: model:ir.model,name:vault.model_vault_abstract_field +#, python-format +msgid "Abstract model to implement basic fields for encryption" +msgstr "Modelo abstracto para implementar los campos básicos de encriptación" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#: model:ir.model,name:vault.model_vault_abstract +#, python-format +msgid "Abstract model to implement general access rights" +msgstr "Modelo abstracto para aplicar los derechos de acceso generales" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__accesses +msgid "Access counter" +msgstr "Contador de acceso" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__active_key +msgid "Active Key" +msgstr "Clave activa" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_entry +#: model:ir.ui.menu,name:vault.menu_vault_entry +msgid "All Entries" +msgstr "Todos los registros" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to export vaults accessible to them" +msgstr "Permitir que todos los usuarios exporten secretos accesibles para ellos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to import vaults accessible to them" +msgstr "Permitir que todos los usuarios importen secretos accesibles para ellos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow the usage to share secrets with external users" +msgstr "Permitir el uso para compartir secretos con usuarios externos" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_create +msgid "Allow to create in the vault" +msgstr "Permitir crear en el almacén de secretos" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_delete +msgid "Allow to delete a vault" +msgstr "Permitir borrar un secreto" + +#. module: vault +#: model:res.groups,name:vault.group_vault_export +msgid "Allow to export vaults" +msgstr "Permitir exportar secretos" + +#. module: vault +#: model:res.groups,name:vault.group_vault_import +msgid "Allow to import vaults" +msgstr "Permitir importar secretos" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_share +msgid "Allow to share a vault with new users" +msgstr "Permitir compartir un secreto con nuevos usuarios" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_write +msgid "Allow to write to the vault" +msgstr "Permitir escribir en un secreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_create +msgid "Allowed Create" +msgstr "Permitido crear" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_delete +msgid "Allowed Delete" +msgstr "Permitido borrar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_read +msgid "Allowed Read" +msgstr "Permitido leer" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_share +msgid "Allowed Share" +msgstr "Permitido añadir usuarios" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_write +msgid "Allowed Write" +msgstr "Permitido escribir" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "An error occured. Please contact the user or administrator" +msgstr "" +"Se ha producido un error. Por favor, póngase en contacto con el usuario o el " +"administrador" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By user" +msgstr "Por usuario" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By vault" +msgstr "Por secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +#, python-format +msgid "Cancel" +msgstr "Cancelar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Cancelled" +msgstr "Cancelado" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Characters:" +msgstr "Caracteres:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__child_ids +msgid "Child" +msgstr "Hijo" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Childs" +msgstr "Hijos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_export_wizard +msgid "Close" +msgstr "Cerrar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__complete_name +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_name +msgid "Complete Name" +msgstr "Nombre completo" + +#. module: vault +#: model:ir.model,name:vault.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de Configuración" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Confirm your password:" +msgstr "Confirma tu contraseña:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Content" +msgstr "Contenido" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Copy to clipboard" +msgstr "Copiar al portapapeles" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_create +msgid "Create" +msgstr "Crear" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_uid +#: model:ir.model.fields,field_description:vault.field_vault__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Created by %(name)s via %(ip)s" +msgstr "Creado por %(name)s a través de %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Created by %s" +msgstr "Creado por %s" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_date +#: model:ir.model.fields,field_description:vault.field_vault__create_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_field__create_date +#: model:ir.model.fields,field_description:vault.field_vault_file__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_right__create_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__crypted_content +msgid "Crypted Content" +msgstr "Contenido encriptado" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__current +msgid "Current" +msgstr "Actual" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Custom JSON format .json" +msgstr "Formato JSON personalizado .json" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__content +msgid "Database" +msgstr "Base de datos" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_delete +msgid "Delete" +msgstr "Borrar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__display_name +#: model:ir.model.fields,field_description:vault.field_vault__display_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__display_name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_field__display_name +#: model:ir.model.fields,field_description:vault.field_vault_file__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_right__display_name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_tag__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Do you really want to create a new key pair and set it active?" +msgstr "¿Realmente quiere crear un nuevo par de claves y activarlo?" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__content +msgid "Download" +msgstr "Descargar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Enter" +msgstr "Ingrese a" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Enter your password:" +msgstr "Ingrese su contraseña:" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_open_entries +#: model:ir.model.fields,field_description:vault.field_vault__entry_ids +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__entry_id +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Entries" +msgstr "Registros" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_log__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__entry_id +msgid "Entry" +msgstr "Registro" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model,name:vault.model_vault_entry +#, python-format +msgid "Entry inside a vault" +msgstr "Entrar al interior de un secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Error" +msgstr "Error" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__expiration +msgid "Expiration" +msgstr "Expiración" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expired +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Expired" +msgstr "Expirado" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expire_date +msgid "Expires on" +msgstr "Expira en" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_export +msgid "Export Vaults" +msgstr "Exportar secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Export to file" +msgstr "Exportar a fichero" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_export_wizard.py:0 +#: model:ir.model,name:vault.model_vault_export_wizard +#, python-format +msgid "Export wizard for vaults" +msgstr "Asistente de exportación para secretos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export keys to object store" +msgstr "Fallo en la exportación de claves al almacén de objetos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export the keys to the database" +msgstr "Fallo en la exportación de claves a la base de datos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to import keys from database" +msgstr "Fallo en la importación de claves desde la base de datos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_field.py:0 +#: model:ir.model,name:vault.model_vault_field +#, python-format +msgid "Field of a vault" +msgstr "Campo de un secreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__field_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__field_ids +msgid "Fields" +msgstr "Campos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_file.py:0 +#: model:ir.model,name:vault.model_vault_file +#, python-format +msgid "File of a vault" +msgstr "Fichero de un secreto" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "File to share:" +msgstr "Fichero a compartir:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__filename +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__filename +msgid "Filename" +msgstr "Nombre del fichero" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__file_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__file_ids +msgid "Files" +msgstr "Ficheros" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__fingerprint +msgid "Fingerprint" +msgstr "Huella digital" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Generate" +msgstr "Generar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Generate a new secret:" +msgstr "Generar nuevo secreto:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "Grouped" +msgstr "Agrupado" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Hide" +msgstr "Ocultar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__id +#: model:ir.model.fields,field_description:vault.field_vault__id +#: model:ir.model.fields,field_description:vault.field_vault_entry__id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_field__id +#: model:ir.model.fields,field_description:vault.field_vault_file__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__id +#: model:ir.model.fields,field_description:vault.field_vault_log__id +#: model:ir.model.fields,field_description:vault.field_vault_right__id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_tag__id +msgid "ID" +msgstr "ID" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__expiration +msgid "If expired the inbox can't be written using the link" +msgstr "" +"Si ha caducado, la bandeja de entrada no puede escribirse utilizando el " +"enlace" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__accesses +msgid "If this is 0 the inbox can't be written using the link" +msgstr "" +"Si esto es 0 la bandeja de entrada no puede ser escrita usando el enlace" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Import" +msgstr "Importar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_import +msgid "Import Vaults" +msgstr "Importar secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Import from file" +msgstr "Importar desde archivo" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard +#, python-format +msgid "Import wizard for vaults" +msgstr "Asistente de importación para secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard_path +#, python-format +msgid "Import wizard path for vaults" +msgstr "Ruta del asistente de importación para secretos" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_inbox +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__inbox_id +#: model:ir.ui.menu,name:vault.menu_vault_inbox +msgid "Inbox" +msgstr "Bandeja de entrada" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_enabled +msgid "Inbox Enabled" +msgstr "Bandeja de entrada habilitada" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_link +#: model:ir.model.fields,field_description:vault.field_vault_inbox__inbox_link +msgid "Inbox Link" +msgstr "Enlace de la bandeja de entrada" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_token +msgid "Inbox Token" +msgstr "Token bandeja de entrada" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__include_childs +msgid "Include Childs" +msgstr "Incluir hijos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Information" +msgstr "Información" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#, python-format +msgid "Invalid file to import from" +msgstr "Archivo inválido para importar" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#, python-format +msgid "Invalid parameter" +msgstr "Parámetro no válido" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Token inválido" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "Invalidate private key" +msgstr "Invalidar clave privada" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iterations +msgid "Iterations" +msgstr "Iteraciones" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iv +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_file__iv +#: model:ir.model.fields,field_description:vault.field_vault_inbox__iv +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__iv +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__iv +msgid "Iv" +msgstr "Iv" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Keepass Database .kdbx" +msgstr "Base de datos keepass .kdbx" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__key +#: model:ir.model.fields,field_description:vault.field_vault_right__key +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__key +msgid "Key" +msgstr "Clave" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/user_menu.esm.js:0 +#, python-format +msgid "Key Management" +msgstr "Gestión de claves" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key_user +msgid "Key User" +msgstr "Clave de usuario" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Keyfile:" +msgstr "Archivo de llaves:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__keys +msgid "Keys" +msgstr "Claves" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key____last_update +#: model:ir.model.fields,field_description:vault.field_vault____last_update +#: model:ir.model.fields,field_description:vault.field_vault_entry____last_update +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_field____last_update +#: model:ir.model.fields,field_description:vault.field_vault_file____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_right____last_update +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_tag____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_uid +#: model:ir.model.fields,field_description:vault.field_vault__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_uid +msgid "Last Updated by" +msgstr "Última modificación por" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_date +#: model:ir.model.fields,field_description:vault.field_vault__write_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_field__write_date +#: model:ir.model.fields,field_description:vault.field_vault_file__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_right__write_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Length:" +msgstr "Longitud:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox__log_ids +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Log" +msgstr "Registro" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#: model:ir.model,name:vault.model_vault_log +#, python-format +msgid "Log entry of a vault" +msgstr "Registro de entrada de un secreto" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_res_users_keys +msgid "Manage my keys" +msgstr "Gestionar mis claves" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__master_key +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_file__master_key +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_right__master_key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__master_key +msgid "Master Key" +msgstr "Clave maestra" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__message +msgid "Message" +msgstr "Mensaje" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Missing filename" +msgstr "Falta el nombre del fichero" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Missing password" +msgstr "Falta la contraseña" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__model +msgid "Model" +msgstr "Modelo" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__module_vault_share +msgid "Module Vault Share" +msgstr "Módulo Vault Share" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__name +#: model:ir.model.fields,field_description:vault.field_vault_entry__name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_field__name +#: model:ir.model.fields,field_description:vault.field_vault_file__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_tag__name +msgid "Name" +msgstr "Nombre" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Name of your secret:" +msgstr "Nombre de tu secreto:" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Neither a secret nor file was given" +msgstr "No se proporcionó un secreto ni un archivo" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New inbox link" +msgstr "Nuevo enlace de la bandeja de entrada" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New private key" +msgstr "Nueva clave privada" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "No secret found" +msgstr "No se ha encontrado ningún secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_inbox_value_check +#: model:ir.model.constraint,message:vault.constraint_vault_send_wizard_value_check +#, python-format +msgid "No value found" +msgstr "No se ha encontrado ningún valor" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Not Expired" +msgstr "No ha caducado" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__note +#: model:ir.model.fields,field_description:vault.field_vault_entry__note +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Note" +msgstr "Nota" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__user_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__user_id +msgid "Owner" +msgstr "Propietario" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__parent_id +msgid "Parent" +msgstr "Padre" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__parent_id +msgid "Parent Entry" +msgstr "Registro padre" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Password:" +msgstr "Contraseña:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__path +msgid "Path to import" +msgstr "Ruta de importación" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_entry__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_file__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_user +msgid "Perm User" +msgstr "Usuario permanente" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/export.esm.js:0 +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the database" +msgstr "Por favor, introduzca la contraseña de la base de datos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the keepass database" +msgstr "Por favor, introduzca la contraseña de la base de datos keepass" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Please enter the password for your private key" +msgstr "Por favor, introduzca la contraseña de su clave privada" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Please enter your password or upload a keyfile:" +msgstr "Por favor, introduzca su contraseña o cargue un archivo de claves:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Please specify a name" +msgstr "Por favor, especifique un nombre" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__private +msgid "Private" +msgstr "Privado" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__public +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__public +msgid "Public" +msgstr "Público" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__public_key +msgid "Public Key" +msgstr "Clave pública" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Re-encrypt" +msgstr "Reencriptar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__reencrypt_required +msgid "Reencrypt Required" +msgstr "Necesita reencriptarse" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_right +#: model:ir.model.fields,field_description:vault.field_vault__right_ids +#: model:ir.ui.menu,name:vault.menu_vault_right +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Rights" +msgstr "Derechos" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__salt +msgid "Salt" +msgstr "Sal" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Save As..." +msgstr "Guardar como..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Save in a vault" +msgstr "Guardar en un vault" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret" +msgstr "Secreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret_file +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret_file +msgid "Secret File" +msgstr "Archivo de secretos" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret_temporary +msgid "Secret Temporary" +msgstr "Secreto temporal" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret to share:" +msgstr "Secreto a compartir:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "Send" +msgstr "Enviar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Send the secret to an user" +msgstr "Enviar secreto a un usuario" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_mixin.esm.js:0 +#, python-format +msgid "Send the secret to another user" +msgstr "Enviar secreto a otro usuario" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_share +msgid "Share" +msgstr "Compartir" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Show" +msgstr "Mostrar" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Something went wrong with the encryption" +msgstr "Algo ha ido mal en el encriptado" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Special" +msgstr "Especial" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__state +msgid "State" +msgstr "Estado" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "Store" +msgstr "Tienda" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_mixin.esm.js:0 +#, python-format +msgid "Store the secret in a vault" +msgstr "Guarda el secreto en un almacén de secretos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Submit secret" +msgstr "Enviar secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Successfully stored" +msgstr "Guardado correctamente" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__tags +msgid "Tags" +msgstr "Etiquetas" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_entry_vault_uuid_uniq +#: model:ir.model.constraint,message:vault.constraint_vault_uuid_uniq +#, python-format +msgid "The UUID must be unique." +msgstr "El UUID debe ser único." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "The field is empty, there's nothing to save!" +msgstr "El campo está vacío, ¡no hay nada que guardar!" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "The files must end on one of the supported file type:" +msgstr "Los archivos deben terminar en uno de los tipos de archivo admitidos:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "The following entries are broken:" +msgstr "Los siguientes registros están rotos:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "The passwords aren't matching" +msgstr "Las contraseñas no coinciden" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#, python-format +msgid "" +"The requested operation can not be completed due to security restrictions." +msgstr "" +"La operación solicitada no puede completarse debido a restricciones de " +"seguridad." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +#, python-format +msgid "The tag must be unique!" +msgstr "¡La etiqueta debe ser única!" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +#, python-format +msgid "The user must be unique" +msgstr "El usuario debe ser único" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__token +msgid "Token" +msgstr "Token" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Unsupported file to import" +msgstr "Archivo no compatible para importar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__url +msgid "Url" +msgstr "Url" + +#. module: vault +#: model:ir.model,name:vault.model_res_users +#: model:ir.model.fields,field_description:vault.field_res_users_key__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__user_id +#: model:ir.model.fields,field_description:vault.field_vault_right__user_id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__user_id +msgid "User" +msgstr "Usuario" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#: model:ir.model,name:vault.model_res_users_key +#, python-format +msgid "User data of a vault" +msgstr "Datos de usuario de un secreto" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__inbox_link +msgid "" +"Using this link you can write to the current inbox. If you want people to " +"create new inboxes you should give them your inbox link from your key " +"management." +msgstr "" +"Con este enlace puedes escribir en la bandeja de entrada actual. Si quieres " +"que la gente cree nuevas bandejas de entrada, deberás darles el enlace de tu " +"bandeja de entrada desde tu gestor de claves." + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__uuid +#: model:ir.model.fields,field_description:vault.field_vault__uuid +#: model:ir.model.fields,field_description:vault.field_vault_entry__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__uuid +msgid "Uuid" +msgstr "Uuid" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_field__value +#: model:ir.model.fields,field_description:vault.field_vault_file__value +msgid "Value" +msgstr "Valor" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: model:ir.actions.act_window,name:vault.action_vault +#: model:ir.model,name:vault.model_vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_file__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_right__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__vault_id +#: model:ir.ui.menu,name:vault.menu_vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#, python-format +msgid "Vault" +msgstr "Secretos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +#, python-format +msgid "Vault Field" +msgstr "Campo de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Vault File" +msgstr "Fichero de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +#, python-format +msgid "Vault Inbox Field" +msgstr "Campo de bandeja de entrada de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_file.esm.js:0 +#, python-format +msgid "Vault Inbox File" +msgstr "Fichero de bandeja de entrada de secreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__vault_right_ids +msgid "Vault Right" +msgstr "Permiso de secretos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Vault Share" +msgstr "Compartir secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox_log.py:0 +#: model:ir.model,name:vault.model_vault_inbox_log +#, python-format +msgid "Vault inbox log" +msgstr "Registro de bandeja de entrada de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Vault is not supported" +msgstr "Secretos no está soportado" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model,name:vault.model_vault_right +#, python-format +msgid "Vault rights" +msgstr "Permisos de secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: model:ir.model,name:vault.model_vault_inbox +#, python-format +msgid "Vault share incoming secrets" +msgstr "Compartir los secretos de entrada de almacén de secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model,name:vault.model_vault_tag +#, python-format +msgid "Vault tag" +msgstr "Etiqueta de vault" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vaults" +msgstr "Secretos" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Verify" +msgstr "Verificar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__version +msgid "Version" +msgstr "Versión" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Warning" +msgstr "Advertencia" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +#: model:ir.model,name:vault.model_vault_store_wizard +#, python-format +msgid "Wizard store a shared secret in a vault" +msgstr "Asistente de almacenado de secreto compartido en un almacén de secretos" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model,name:vault.model_vault_send_wizard +#, python-format +msgid "Wizard to send another user a secret" +msgstr "Asistente para enviar un secreto a otro usuario" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_write +msgid "Write" +msgstr "Escribir" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Written by %(name)s via %(ip)s" +msgstr "Escrito por %(name)s vía %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "You can not create recursive entries." +msgstr "No puedes crear registros recursivos." + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "" +"You can only send the secret to the user who has generated a key-pair.\n" +" If an user is not showing please ask him to generate " +"these." +msgstr "" +"Sólo se puede enviar el secreto al usuario que ha generado un par de " +"claves.\n" +" Si un usuario no aparece, por favor, pídale que los " +"genere." + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "" +"You will loose access to all vaults and your inbox. Do you want to continue?" +msgstr "" +"Perderá el acceso a todos sus secretos y a su bandeja de entrada. ¿Desea " +"continuar?" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "a-z" +msgstr "a-z" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "or" +msgstr "o" + +#~ msgid "Users" +#~ msgstr "Usuarios" + +#, python-format +#~ msgid "" +#~ "This will re-encrypt everything in the vault. Do you want to proceed?" +#~ msgstr "Esto volverá a encriptar todo en el vault. ¿Quieres proceder?" + +#, python-format +#~ msgid "%s entry %s by %s" +#~ msgstr "%s registrado %s por %s" + +#, python-format +#~ msgid "Created by %s via %s" +#~ msgstr "Creado por %s via %s" + +#, python-format +#~ msgid "Written by %s via %s" +#~ msgstr "Escrito por %s via %s" diff --git a/vault/i18n/it.po b/vault/i18n/it.po new file mode 100644 index 0000000000..fa3a113ca6 --- /dev/null +++ b/vault/i18n/it.po @@ -0,0 +1,1503 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-03-12 12:05+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s inserisce %(name)s da %(user)s" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "%s '%s' of entry '%s'" +msgstr "%s '%s' dell'inserimento '%s'" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%s (copy)" +msgstr "%s (copia)" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "" +"A secure browser context is required. Please switch to https or contact your" +" administrator" +msgstr "" +"È richiesto un ambiente browser sicuro. Passare ad HTTPS o contattare " +"l'amministratore" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault_field.py:0 +#: model:ir.model,name:vault.model_vault_abstract_field +#, python-format +msgid "Abstract model to implement basic fields for encryption" +msgstr "Modello astratto per implementare campi base per la criptazione" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#: model:ir.model,name:vault.model_vault_abstract +#, python-format +msgid "Abstract model to implement general access rights" +msgstr "Modello astratto per implementare diritto di accesso generali" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__accesses +msgid "Access counter" +msgstr "Contatore accessi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__active_key +msgid "Active Key" +msgstr "Chiave attiva" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_entry +#: model:ir.ui.menu,name:vault.menu_vault_entry +msgid "All Entries" +msgstr "Tutte le registrazioni" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to export vaults accessible to them" +msgstr "" +"Consente a tutti gli utenti di esportare gli depositidi sicurezza da loro " +"accessibili" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to import vaults accessible to them" +msgstr "" +"Consente a tutti gli utenti di importare depositi di sicurezza da loro " +"accessibili" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow the usage to share secrets with external users" +msgstr "Consente l'utilizzo di segreti condivisi con utenti esterni" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_create +msgid "Allow to create in the vault" +msgstr "Consente di creare nell'deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_delete +msgid "Allow to delete a vault" +msgstr "Consenti di eliminare un deposito di sicurezza" + +#. module: vault +#: model:res.groups,name:vault.group_vault_export +msgid "Allow to export vaults" +msgstr "Consente di esportare depositi di sicurezza" + +#. module: vault +#: model:res.groups,name:vault.group_vault_import +msgid "Allow to import vaults" +msgstr "Consente di importare depositi di sicurezza" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_share +msgid "Allow to share a vault with new users" +msgstr "Consente di condividere un deposito di sicurezza con nuovi utenti" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_write +msgid "Allow to write to the vault" +msgstr "Consente di scrivere nell'deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_create +msgid "Allowed Create" +msgstr "Autorizzati a creare" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_delete +msgid "Allowed Delete" +msgstr "Autorizzati a cancellare" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_read +msgid "Allowed Read" +msgstr "Autorizzati alla lettura" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_share +msgid "Allowed Share" +msgstr "Autorizzati a condividere" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_write +msgid "Allowed Write" +msgstr "Autorizzati alla scrittura" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "An error occured. Please contact the user or administrator" +msgstr "Si è verificato un errore. Contattare l'utente o l'amministratore" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By user" +msgstr "Per utente" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By vault" +msgstr "Per valore" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +#, python-format +msgid "Cancel" +msgstr "Annulla" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Cancelled" +msgstr "Annullato" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Characters:" +msgstr "Caratteri:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__child_ids +msgid "Child" +msgstr "Figlio" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Childs" +msgstr "Figli" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_export_wizard +msgid "Close" +msgstr "Chiudi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__complete_name +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_name +msgid "Complete Name" +msgstr "Nome completo" + +#. module: vault +#: model:ir.model,name:vault.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Confirm your password:" +msgstr "Conferma password:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Content" +msgstr "Contenuto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Copy to clipboard" +msgstr "Copia negli appunti" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_create +msgid "Create" +msgstr "Crea" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_uid +#: model:ir.model.fields,field_description:vault.field_vault__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Created by %(name)s via %(ip)s" +msgstr "Creato da %(name)s attraverso %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Created by %s" +msgstr "Creato da %s" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_date +#: model:ir.model.fields,field_description:vault.field_vault__create_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_field__create_date +#: model:ir.model.fields,field_description:vault.field_vault_file__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_right__create_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__crypted_content +msgid "Crypted Content" +msgstr "Contenuto criptato" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__current +msgid "Current" +msgstr "Attuale" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Custom JSON format .json" +msgstr "JSON personalizzato formato .json" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__content +msgid "Database" +msgstr "Database" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_delete +msgid "Delete" +msgstr "Cancella" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__display_name +#: model:ir.model.fields,field_description:vault.field_vault__display_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__display_name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_field__display_name +#: model:ir.model.fields,field_description:vault.field_vault_file__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_right__display_name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_tag__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Do you really want to create a new key pair and set it active?" +msgstr "Si vuole veramente creare un nuovo paio di chiavi e impostare attive?" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__content +msgid "Download" +msgstr "Scarica" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Enter" +msgstr "Inserire" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Enter your password:" +msgstr "Inserire password:" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_open_entries +#: model:ir.model.fields,field_description:vault.field_vault__entry_ids +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__entry_id +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Entries" +msgstr "Voci" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_log__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__entry_id +msgid "Entry" +msgstr "Voce" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model,name:vault.model_vault_entry +#, python-format +msgid "Entry inside a vault" +msgstr "Voce in un deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Error" +msgstr "Errore" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__expiration +msgid "Expiration" +msgstr "Scadenza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expired +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Expired" +msgstr "Scaduta" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expire_date +msgid "Expires on" +msgstr "Scade il" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_export +msgid "Export Vaults" +msgstr "Esporta depositi di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Export to file" +msgstr "Esporta in un file" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_export_wizard.py:0 +#: model:ir.model,name:vault.model_vault_export_wizard +#, python-format +msgid "Export wizard for vaults" +msgstr "Procedura guidata esportazione depositi di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export keys to object store" +msgstr "Fall'ita l'esportazione delle chiavi nell'oggetto deposito" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export the keys to the database" +msgstr "Fallita l'esportazione delle chiavi nel database" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to import keys from database" +msgstr "Fall'ita l'importazione delle chiavi dal database" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_field.py:0 +#: model:ir.model,name:vault.model_vault_field +#, python-format +msgid "Field of a vault" +msgstr "Campo di un deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__field_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__field_ids +msgid "Fields" +msgstr "Campi" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_file.py:0 +#: model:ir.model,name:vault.model_vault_file +#, python-format +msgid "File of a vault" +msgstr "File di un deposito di sicurezza" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "File to share:" +msgstr "File da condividere:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__filename +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__filename +msgid "Filename" +msgstr "Nome file" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__file_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__file_ids +msgid "Files" +msgstr "File" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__fingerprint +msgid "Fingerprint" +msgstr "Impronta" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Generate" +msgstr "Genera" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Generate a new secret:" +msgstr "Genera un nuovo segreto:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "Grouped" +msgstr "Raggruppati" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Hide" +msgstr "Nascondi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__id +#: model:ir.model.fields,field_description:vault.field_vault__id +#: model:ir.model.fields,field_description:vault.field_vault_entry__id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_field__id +#: model:ir.model.fields,field_description:vault.field_vault_file__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__id +#: model:ir.model.fields,field_description:vault.field_vault_log__id +#: model:ir.model.fields,field_description:vault.field_vault_right__id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_tag__id +msgid "ID" +msgstr "ID" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__expiration +msgid "If expired the inbox can't be written using the link" +msgstr "" +"Se scaduto il contenitore di arrivo none può essere scritto utilizzando il " +"collegamento" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__accesses +msgid "If this is 0 the inbox can't be written using the link" +msgstr "" +"Se questo è 0 il contenitore di arrivo non può essere scritto utilizzando il " +"collegamento" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Import" +msgstr "Importa" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_import +msgid "Import Vaults" +msgstr "Importa depositi di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Import from file" +msgstr "Importa da file" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard +#, python-format +msgid "Import wizard for vaults" +msgstr "Procedura guidata importazione depositi di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard_path +#, python-format +msgid "Import wizard path for vaults" +msgstr "Percorso procedura guidata importazione depositi di sicurezza" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_inbox +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__inbox_id +#: model:ir.ui.menu,name:vault.menu_vault_inbox +msgid "Inbox" +msgstr "Deposito in arrivo" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_enabled +msgid "Inbox Enabled" +msgstr "Deposito in arrivo abilitato" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_link +#: model:ir.model.fields,field_description:vault.field_vault_inbox__inbox_link +msgid "Inbox Link" +msgstr "Collegamento deposito in arrivo" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_token +msgid "Inbox Token" +msgstr "Token deposito in arrivo" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__include_childs +msgid "Include Childs" +msgstr "Includi figli" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Information" +msgstr "Informazioni" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#, python-format +msgid "Invalid file to import from" +msgstr "File da cui importare non valido" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#: code:addons/vault/models/res_users_key.py:0 +#: code:addons/vault/models/res_users_key.py:0 +#, python-format +msgid "Invalid parameter" +msgstr "Parametro non valido" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Token non valido" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "Invalidate private key" +msgstr "Invalida chiave privata" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iterations +msgid "Iterations" +msgstr "Iterazioni" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iv +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_file__iv +#: model:ir.model.fields,field_description:vault.field_vault_inbox__iv +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__iv +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__iv +msgid "Iv" +msgstr "Iv" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Keepass Database .kdbx" +msgstr "Database Keepass .kdbx" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__key +#: model:ir.model.fields,field_description:vault.field_vault_right__key +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__key +msgid "Key" +msgstr "Chiave" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/user_menu.esm.js:0 +#, python-format +msgid "Key Management" +msgstr "Gestione chiave" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key_user +msgid "Key User" +msgstr "Utente chiave" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Keyfile:" +msgstr "File chiave:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__keys +msgid "Keys" +msgstr "Chiavi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key____last_update +#: model:ir.model.fields,field_description:vault.field_vault____last_update +#: model:ir.model.fields,field_description:vault.field_vault_entry____last_update +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_field____last_update +#: model:ir.model.fields,field_description:vault.field_vault_file____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_right____last_update +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_tag____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_uid +#: model:ir.model.fields,field_description:vault.field_vault__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_date +#: model:ir.model.fields,field_description:vault.field_vault__write_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_field__write_date +#: model:ir.model.fields,field_description:vault.field_vault_file__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_right__write_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Length:" +msgstr "Lunghezza:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox__log_ids +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Log" +msgstr "Log" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#: model:ir.model,name:vault.model_vault_log +#, python-format +msgid "Log entry of a vault" +msgstr "Registra valori di un deposito di sicurezza" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_res_users_keys +msgid "Manage my keys" +msgstr "Gestione mie chiavi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__master_key +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_file__master_key +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_right__master_key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__master_key +msgid "Master Key" +msgstr "Chiave master" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__message +msgid "Message" +msgstr "Messaggio" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Missing filename" +msgstr "Nome file mancante" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Missing password" +msgstr "Password mancante" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__model +msgid "Model" +msgstr "Modello" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__module_vault_share +msgid "Module Vault Share" +msgstr "Modulo condivisione deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__name +#: model:ir.model.fields,field_description:vault.field_vault_entry__name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_field__name +#: model:ir.model.fields,field_description:vault.field_vault_file__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_tag__name +msgid "Name" +msgstr "Nome" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Name of your secret:" +msgstr "Nome del segreto:" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Neither a secret nor file was given" +msgstr "Non è stato fornito ne un segreto ne un file" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New inbox link" +msgstr "Nuovo collegamento deposito in ingresso" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New private key" +msgstr "Nuova chiave privata" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "No secret found" +msgstr "Nessun segreto trovato" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_inbox_value_check +#: model:ir.model.constraint,message:vault.constraint_vault_send_wizard_value_check +#, python-format +msgid "No value found" +msgstr "Nessun valore trovato" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Not Expired" +msgstr "Non scaduto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__note +#: model:ir.model.fields,field_description:vault.field_vault_entry__note +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Note" +msgstr "Nota" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__user_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__user_id +msgid "Owner" +msgstr "Proprietario" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__parent_id +msgid "Parent" +msgstr "Padre" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__parent_id +msgid "Parent Entry" +msgstr "Valore padre" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Password:" +msgstr "Password:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__path +msgid "Path to import" +msgstr "Percorso di importazione" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_entry__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_file__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_user +msgid "Perm User" +msgstr "Utente permanente" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/export.esm.js:0 +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the database" +msgstr "Inserire la password per il database" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the keepass database" +msgstr "Inserire la password per il database Keepass" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Please enter the password for your private key" +msgstr "Inserire la password per la chiave privata" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Please enter your password or upload a keyfile:" +msgstr "Inserire la password o caricare un file chiave:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Please specify a name" +msgstr "Indicare un nome" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__private +msgid "Private" +msgstr "Privato" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__public +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__public +msgid "Public" +msgstr "Pubblico" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__public_key +msgid "Public Key" +msgstr "Chiave pubblica" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Re-encrypt" +msgstr "Ri-encripta" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__reencrypt_required +msgid "Reencrypt Required" +msgstr "Richiesto ricriptazione" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_right +#: model:ir.model.fields,field_description:vault.field_vault__right_ids +#: model:ir.ui.menu,name:vault.menu_vault_right +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Rights" +msgstr "Diritti" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__salt +msgid "Salt" +msgstr "Salt" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Save As..." +msgstr "Salva come..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Save in a vault" +msgstr "Salva in un deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret" +msgstr "Segreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret_file +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret_file +msgid "Secret File" +msgstr "File segreto" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret_temporary +msgid "Secret Temporary" +msgstr "Segreto provvisorio" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret to share:" +msgstr "Segreto da condividere:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "Send" +msgstr "Invia" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Send the secret to an user" +msgstr "Invia segreto ad un utente" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_mixin.esm.js:0 +#, python-format +msgid "Send the secret to another user" +msgstr "Invia segreto ad un altro utente" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_share +msgid "Share" +msgstr "Condividi" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Show" +msgstr "Mostra" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Something went wrong with the encryption" +msgstr "Qualcosa è andato male con la criptazione" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Special" +msgstr "Speciale" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__state +msgid "State" +msgstr "Stato" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "Store" +msgstr "Archivia" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_mixin.esm.js:0 +#, python-format +msgid "Store the secret in a vault" +msgstr "Archivia segreto in un deposito di sicurezza" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Submit secret" +msgstr "Invia segreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Successfully stored" +msgstr "Archiviato con successo" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__tags +msgid "Tags" +msgstr "Etichette" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_entry_vault_uuid_uniq +#: model:ir.model.constraint,message:vault.constraint_vault_uuid_uniq +#, python-format +msgid "The UUID must be unique." +msgstr "Il codice UUID deve essere univoco." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "The field is empty, there's nothing to save!" +msgstr "Il campo è vuoto, non c'è niente da salvare!" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "The files must end on one of the supported file type:" +msgstr "Il file deve terminare con un tipo file supportato:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "The following entries are broken:" +msgstr "Le seguenti voci sono rovinate:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "The passwords aren't matching" +msgstr "Le password non corrispondono" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#, python-format +msgid "" +"The requested operation can not be completed due to security restrictions." +msgstr "" +"L'operazione richiesta non può essere completata per restrizioni di " +"sicurezza." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +#, python-format +msgid "The tag must be unique!" +msgstr "L'etichetta deve essere univoca!" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +#, python-format +msgid "The user must be unique" +msgstr "L'utente deve essere univoco" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__token +msgid "Token" +msgstr "Token" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Unsupported file to import" +msgstr "File non supportati per l'importazione" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__url +msgid "Url" +msgstr "URL" + +#. module: vault +#: model:ir.model,name:vault.model_res_users +#: model:ir.model.fields,field_description:vault.field_res_users_key__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__user_id +#: model:ir.model.fields,field_description:vault.field_vault_right__user_id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__user_id +msgid "User" +msgstr "Utente" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#: model:ir.model,name:vault.model_res_users_key +#, python-format +msgid "User data of a vault" +msgstr "Dati utente di un deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__inbox_link +msgid "" +"Using this link you can write to the current inbox. If you want people to " +"create new inboxes you should give them your inbox link from your key " +"management." +msgstr "" +"Utilizzando questo collegamento si può scrivere nel deposito di ingresso " +"attuale. Se si vuole che le persone creino nuovi depositi di ingresso " +"bisogna fornirgli il collegamento al proprio deposito di ingresso dalla " +"gestione della propria chiave." + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__uuid +#: model:ir.model.fields,field_description:vault.field_vault__uuid +#: model:ir.model.fields,field_description:vault.field_vault_entry__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__uuid +msgid "Uuid" +msgstr "UUID" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_field__value +#: model:ir.model.fields,field_description:vault.field_vault_file__value +msgid "Value" +msgstr "Valore" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: model:ir.actions.act_window,name:vault.action_vault +#: model:ir.model,name:vault.model_vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_file__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_right__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__vault_id +#: model:ir.ui.menu,name:vault.menu_vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#, python-format +msgid "Vault" +msgstr "Deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +#, python-format +msgid "Vault Field" +msgstr "Campo deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Vault File" +msgstr "File deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +#, python-format +msgid "Vault Inbox Field" +msgstr "Campo archivio in ingresso deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_file.esm.js:0 +#, python-format +msgid "Vault Inbox File" +msgstr "File archivio in ingresso deposito di sicurezza" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__vault_right_ids +msgid "Vault Right" +msgstr "Autorizzazioni deposito di sicurezza" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Vault Share" +msgstr "Condivisione deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox_log.py:0 +#: model:ir.model,name:vault.model_vault_inbox_log +#, python-format +msgid "Vault inbox log" +msgstr "Log archivio in ingresso deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Vault is not supported" +msgstr "Il deposito di sicurezza non è supportato" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model,name:vault.model_vault_right +#, python-format +msgid "Vault rights" +msgstr "Autorizzazioni deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: model:ir.model,name:vault.model_vault_inbox +#, python-format +msgid "Vault share incoming secrets" +msgstr "Segreti in ingresso condivisione deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model,name:vault.model_vault_tag +#, python-format +msgid "Vault tag" +msgstr "Etichetta deposito di sicurezza" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vaults" +msgstr "Depositi di sicurezza" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Verify" +msgstr "Verifica" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__version +msgid "Version" +msgstr "Versione" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Warning" +msgstr "Attenzione" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +#: model:ir.model,name:vault.model_vault_store_wizard +#, python-format +msgid "Wizard store a shared secret in a vault" +msgstr "" +"La procedura guidata salva un segreto condiviso in un deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model,name:vault.model_vault_send_wizard +#, python-format +msgid "Wizard to send another user a secret" +msgstr "Procedura guidata per inviare un segreto ad un altro utente" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_write +msgid "Write" +msgstr "Scrivi" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Written by %(name)s via %(ip)s" +msgstr "Scritto da %(name)s attraverso %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "You can not create recursive entries." +msgstr "Non si possono creare voci ricorsive." + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "" +"You can only send the secret to the user who has generated a key-pair.\n" +" If an user is not showing please ask him to generate these." +msgstr "" +"Si possono inviare segreti solo all'utente che ha generato una coppia di " +"chiavi.\n" +" Se un utente non è visibile chiedergli di generarle." + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "" +"You will loose access to all vaults and your inbox. Do you want to continue?" +msgstr "" +"Si perderà l'accesso a tutti i depositi di sicurezza e aldeposito in " +"ingresso. Si vuole continuare?" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "a-z" +msgstr "a-z" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "or" +msgstr "o" diff --git a/vault/i18n/nl.po b/vault/i18n/nl.po new file mode 100644 index 0000000000..eeb7fc93f9 --- /dev/null +++ b/vault/i18n/nl.po @@ -0,0 +1,1477 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-05-05 10:45+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s toegang %(name)s door %(user)s" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "%s '%s' of entry '%s'" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "%s (copy)" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "" +"A secure browser context is required. Please switch to https or contact your " +"administrator" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault_field.py:0 +#: model:ir.model,name:vault.model_vault_abstract_field +#, python-format +msgid "Abstract model to implement basic fields for encryption" +msgstr "Abstract model om basisvelden voor encryptie te implementeren" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#: model:ir.model,name:vault.model_vault_abstract +#, python-format +msgid "Abstract model to implement general access rights" +msgstr "Abstract model om algemene toegangsrechten te implementeren" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__accesses +msgid "Access counter" +msgstr "Toegangsteller" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__active_key +msgid "Active Key" +msgstr "Actieve sleutel" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_entry +#: model:ir.ui.menu,name:vault.menu_vault_entry +msgid "All Entries" +msgstr "Alle invoeren" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to export vaults accessible to them" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to import vaults accessible to them" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow the usage to share secrets with external users" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_create +msgid "Allow to create in the vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_delete +msgid "Allow to delete a vault" +msgstr "" + +#. module: vault +#: model:res.groups,name:vault.group_vault_export +msgid "Allow to export vaults" +msgstr "" + +#. module: vault +#: model:res.groups,name:vault.group_vault_import +msgid "Allow to import vaults" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_share +msgid "Allow to share a vault with new users" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_write +msgid "Allow to write to the vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_create +msgid "Allowed Create" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_delete +msgid "Allowed Delete" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_read +msgid "Allowed Read" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_share +msgid "Allowed Share" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_write +msgid "Allowed Write" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "An error occured. Please contact the user or administrator" +msgstr "" +"Er is een fout opgetreden. Neem contact op met de gebruiker of beheerder" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By user" +msgstr "Door gebruiker" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By vault" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +#, python-format +msgid "Cancel" +msgstr "Annuleer" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Cancelled" +msgstr "Geannuleerd" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Characters:" +msgstr "Karakters:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__child_ids +msgid "Child" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Childs" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_export_wizard +msgid "Close" +msgstr "Sluiten" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__complete_name +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_name +msgid "Complete Name" +msgstr "Volledige naam" + +#. module: vault +#: model:ir.model,name:vault.model_res_config_settings +msgid "Config Settings" +msgstr "Configuratie-instellingen" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Confirm your password:" +msgstr "Bevestig uw wachtwoord:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Content" +msgstr "Inhoud" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Copy to clipboard" +msgstr "Kopiëren naar klembord" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_create +msgid "Create" +msgstr "Aanmaken" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_uid +#: model:ir.model.fields,field_description:vault.field_vault__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Created by %(name)s via %(ip)s" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Created by %s" +msgstr "Aangemaakt door %s" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_date +#: model:ir.model.fields,field_description:vault.field_vault__create_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_field__create_date +#: model:ir.model.fields,field_description:vault.field_vault_file__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_right__create_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__crypted_content +msgid "Crypted Content" +msgstr "Versleutelde inhoud" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__current +msgid "Current" +msgstr "Huidig" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Custom JSON format .json" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__content +msgid "Database" +msgstr "Database" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_delete +msgid "Delete" +msgstr "Verwijder" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__display_name +#: model:ir.model.fields,field_description:vault.field_vault__display_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__display_name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_field__display_name +#: model:ir.model.fields,field_description:vault.field_vault_file__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_right__display_name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_tag__display_name +msgid "Display Name" +msgstr "Schermnaam" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Do you really want to create a new key pair and set it active?" +msgstr "Wilt u echt een nieuw sleutelpaar aanmaken en activeren?" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__content +msgid "Download" +msgstr "Download" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Enter" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Enter your password:" +msgstr "Voer uw wachtwoord in:" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_open_entries +#: model:ir.model.fields,field_description:vault.field_vault__entry_ids +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__entry_id +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Entries" +msgstr "Boekingen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_log__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__entry_id +msgid "Entry" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model,name:vault.model_vault_entry +#, python-format +msgid "Entry inside a vault" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Error" +msgstr "Fout" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__expiration +msgid "Expiration" +msgstr "Vervaldatum" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expired +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Expired" +msgstr "Verlopen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expire_date +msgid "Expires on" +msgstr "Verloopt op" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_export +msgid "Export Vaults" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Export to file" +msgstr "Export naar bestand" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_export_wizard.py:0 +#: model:ir.model,name:vault.model_vault_export_wizard +#, python-format +msgid "Export wizard for vaults" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export keys to object store" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to export the keys to the database" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Failed to import keys from database" +msgstr "Kan geen sleutels uit database importeren" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_field.py:0 +#: model:ir.model,name:vault.model_vault_field +#, python-format +msgid "Field of a vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__field_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__field_ids +msgid "Fields" +msgstr "Velden" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_file.py:0 +#: model:ir.model,name:vault.model_vault_file +#, python-format +msgid "File of a vault" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "File to share:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__filename +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__filename +msgid "Filename" +msgstr "Bestandsnaam" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__file_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__file_ids +msgid "Files" +msgstr "Bestanden" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__fingerprint +msgid "Fingerprint" +msgstr "Fingerprint" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Generate" +msgstr "Genereer" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Generate a new secret:" +msgstr "Genereer een nieuw geheim:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "Grouped" +msgstr "Gegroepeerd" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Hide" +msgstr "Verbergen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__id +#: model:ir.model.fields,field_description:vault.field_vault__id +#: model:ir.model.fields,field_description:vault.field_vault_entry__id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_field__id +#: model:ir.model.fields,field_description:vault.field_vault_file__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__id +#: model:ir.model.fields,field_description:vault.field_vault_log__id +#: model:ir.model.fields,field_description:vault.field_vault_right__id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_tag__id +msgid "ID" +msgstr "ID" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__expiration +msgid "If expired the inbox can't be written using the link" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__accesses +msgid "If this is 0 the inbox can't be written using the link" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Import" +msgstr "Import" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_import +msgid "Import Vaults" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +#, python-format +msgid "Import from file" +msgstr "Importeren uit bestand" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard +#, python-format +msgid "Import wizard for vaults" +msgstr "Wizard voor het importeren van kluizen" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#: model:ir.model,name:vault.model_vault_import_wizard_path +#, python-format +msgid "Import wizard path for vaults" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_inbox +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__inbox_id +#: model:ir.ui.menu,name:vault.menu_vault_inbox +msgid "Inbox" +msgstr "Inbox" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_enabled +msgid "Inbox Enabled" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_link +#: model:ir.model.fields,field_description:vault.field_vault_inbox__inbox_link +msgid "Inbox Link" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_token +msgid "Inbox Token" +msgstr "Inbox Token" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__include_childs +msgid "Include Childs" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Information" +msgstr "Informatie" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +#, python-format +msgid "Invalid file to import from" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#, python-format +msgid "Invalid parameter" +msgstr "Ongeldige parameter" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Ongeldige token" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "Invalidate private key" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iterations +msgid "Iterations" +msgstr "Iteraties" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iv +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_file__iv +#: model:ir.model.fields,field_description:vault.field_vault_inbox__iv +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__iv +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__iv +msgid "Iv" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Keepass Database .kdbx" +msgstr "Keepass Database .kdbx" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__key +#: model:ir.model.fields,field_description:vault.field_vault_right__key +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__key +msgid "Key" +msgstr "Sleutel" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/user_menu.esm.js:0 +#, python-format +msgid "Key Management" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key_user +msgid "Key User" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Keyfile:" +msgstr "Keyfile:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__keys +msgid "Keys" +msgstr "Keys" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key____last_update +#: model:ir.model.fields,field_description:vault.field_vault____last_update +#: model:ir.model.fields,field_description:vault.field_vault_entry____last_update +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_field____last_update +#: model:ir.model.fields,field_description:vault.field_vault_file____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox____last_update +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_log____last_update +#: model:ir.model.fields,field_description:vault.field_vault_right____last_update +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard____last_update +#: model:ir.model.fields,field_description:vault.field_vault_tag____last_update +msgid "Last Modified on" +msgstr "Laatst gewijzigd op" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_uid +#: model:ir.model.fields,field_description:vault.field_vault__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_uid +msgid "Last Updated by" +msgstr "Laatst bijgewerkt door" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_date +#: model:ir.model.fields,field_description:vault.field_vault__write_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_field__write_date +#: model:ir.model.fields,field_description:vault.field_vault_file__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_right__write_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_date +msgid "Last Updated on" +msgstr "Laatst bijgewerkt op" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Length:" +msgstr "Lengte:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox__log_ids +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Log" +msgstr "Log" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#: model:ir.model,name:vault.model_vault_log +#, python-format +msgid "Log entry of a vault" +msgstr "Logboekvermelding van een kluis" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_res_users_keys +msgid "Manage my keys" +msgstr "Beheer mijn sleutels" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__master_key +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_file__master_key +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_right__master_key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__master_key +msgid "Master Key" +msgstr "Hoofdsleutel" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__message +msgid "Message" +msgstr "Bericht" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Missing filename" +msgstr "Ontbrekende bestandsnaam" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "Missing password" +msgstr "Ontbrekend wachtwoord" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__model +msgid "Model" +msgstr "Type" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__module_vault_share +msgid "Module Vault Share" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__name +#: model:ir.model.fields,field_description:vault.field_vault_entry__name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_field__name +#: model:ir.model.fields,field_description:vault.field_vault_file__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_tag__name +msgid "Name" +msgstr "Naam" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Name of your secret:" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#, python-format +msgid "Neither a secret nor file was given" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New inbox link" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New private key" +msgstr "Nieuwe privésleutel" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "No secret found" +msgstr "Geen geheim gevonden" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_inbox_value_check +#: model:ir.model.constraint,message:vault.constraint_vault_send_wizard_value_check +#, python-format +msgid "No value found" +msgstr "Geen waarde gevonden" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Not Expired" +msgstr "Niet verlopen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__note +#: model:ir.model.fields,field_description:vault.field_vault_entry__note +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Note" +msgstr "Notitie" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__user_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__user_id +msgid "Owner" +msgstr "Eigenaar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__parent_id +msgid "Parent" +msgstr "Bovenliggend" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__parent_id +msgid "Parent Entry" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Password:" +msgstr "Wachtwoord:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__path +msgid "Path to import" +msgstr "Pad om te importeren" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_entry__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_file__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_user +msgid "Perm User" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/export.esm.js:0 +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the database" +msgstr "Voer het wachtwoord van de database in" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Please enter the password for the keepass database" +msgstr "Voer het wachtwoord van de keepass database in" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +#, python-format +msgid "Please enter the password for your private key" +msgstr "Voer het wachtwoord voor uw privésleutel in" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Please enter your password or upload a keyfile:" +msgstr "Voer uw wachtwoord in of upload een keyfile:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Please specify a name" +msgstr "Geef een naam op" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__private +msgid "Private" +msgstr "Privé" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__public +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__public +msgid "Public" +msgstr "Openbaar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__public_key +msgid "Public Key" +msgstr "Openbare sleutel" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Re-encrypt" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__reencrypt_required +msgid "Reencrypt Required" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_right +#: model:ir.model.fields,field_description:vault.field_vault__right_ids +#: model:ir.ui.menu,name:vault.menu_vault_right +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Rights" +msgstr "Rechten" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__salt +msgid "Salt" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Save As..." +msgstr "Opslaan als..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Save in a vault" +msgstr "Opslaan in een kluis" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret" +msgstr "Geheim" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret_file +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret_file +msgid "Secret File" +msgstr "Secret File" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret_temporary +msgid "Secret Temporary" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret to share:" +msgstr "Geheim om te delen:" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "Send" +msgstr "Verzenden" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Send the secret to an user" +msgstr "Verzend het geheim naar een gebruiker" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_mixin.esm.js:0 +#, python-format +msgid "Send the secret to another user" +msgstr "Verzend het geheim naar een andere gebruiker" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_share +msgid "Share" +msgstr "Delen" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Show" +msgstr "Toon" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Something went wrong with the encryption" +msgstr "Er ging iets mis met de encryptie" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "Special" +msgstr "Speciaal" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__state +msgid "State" +msgstr "Status" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "Store" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_mixin.esm.js:0 +#, python-format +msgid "Store the secret in a vault" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Submit secret" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +#, python-format +msgid "Successfully stored" +msgstr "Succesvol opgeslagen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__tags +msgid "Tags" +msgstr "Labels" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_entry_vault_uuid_uniq +#: model:ir.model.constraint,message:vault.constraint_vault_uuid_uniq +#, python-format +msgid "The UUID must be unique." +msgstr "De UUID moet uniek zijn." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "The field is empty, there's nothing to save!" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "The files must end on one of the supported file type:" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "The following entries are broken:" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/utils.esm.js:0 +#, python-format +msgid "The passwords aren't matching" +msgstr "De wachtwoorden komen niet overeen" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +#, python-format +msgid "" +"The requested operation can not be completed due to security restrictions." +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +#, python-format +msgid "The tag must be unique!" +msgstr "Het label moet uniek zijn!" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +#, python-format +msgid "The user must be unique" +msgstr "De gebruiker moet uniek zijn" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__token +msgid "Token" +msgstr "Token" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +#, python-format +msgid "Unsupported file to import" +msgstr "Niet ondersteund bestand voor import" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__url +msgid "Url" +msgstr "Url" + +#. module: vault +#: model:ir.model,name:vault.model_res_users +#: model:ir.model.fields,field_description:vault.field_res_users_key__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__user_id +#: model:ir.model.fields,field_description:vault.field_vault_right__user_id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__user_id +msgid "User" +msgstr "Gebruiker" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +#: model:ir.model,name:vault.model_res_users_key +#, python-format +msgid "User data of a vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__inbox_link +msgid "" +"Using this link you can write to the current inbox. If you want people to " +"create new inboxes you should give them your inbox link from your key " +"management." +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__uuid +#: model:ir.model.fields,field_description:vault.field_vault__uuid +#: model:ir.model.fields,field_description:vault.field_vault_entry__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__uuid +msgid "Uuid" +msgstr "Uuid" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_field__value +#: model:ir.model.fields,field_description:vault.field_vault_file__value +msgid "Value" +msgstr "Waarde" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: model:ir.actions.act_window,name:vault.action_vault +#: model:ir.model,name:vault.model_vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_file__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_right__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__vault_id +#: model:ir.ui.menu,name:vault.menu_vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#, python-format +msgid "Vault" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +#, python-format +msgid "Vault Field" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +#, python-format +msgid "Vault File" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +#, python-format +msgid "Vault Inbox Field" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_file.esm.js:0 +#, python-format +msgid "Vault Inbox File" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__vault_right_ids +msgid "Vault Right" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Vault Share" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox_log.py:0 +#: model:ir.model,name:vault.model_vault_inbox_log +#, python-format +msgid "Vault inbox log" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +#, python-format +msgid "Vault is not supported" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_right.py:0 +#: model:ir.model,name:vault.model_vault_right +#, python-format +msgid "Vault rights" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#: model:ir.model,name:vault.model_vault_inbox +#, python-format +msgid "Vault share incoming secrets" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_tag.py:0 +#: model:ir.model,name:vault.model_vault_tag +#, python-format +msgid "Vault tag" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vaults" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Verify" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__version +msgid "Version" +msgstr "Versie" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +#, python-format +msgid "Warning" +msgstr "Waarschuwing" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +#: model:ir.model,name:vault.model_vault_store_wizard +#, python-format +msgid "Wizard store a shared secret in a vault" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +#: model:ir.model,name:vault.model_vault_send_wizard +#, python-format +msgid "Wizard to send another user a secret" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_write +msgid "Write" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +#, python-format +msgid "Written by %(name)s via %(ip)s" +msgstr "Geschreven door %(name)s via %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +#, python-format +msgid "You can not create recursive entries." +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "" +"You can only send the secret to the user who has generated a key-pair.\n" +" If an user is not showing please ask him to generate " +"these." +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "" +"You will loose access to all vaults and your inbox. Do you want to continue?" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#, python-format +msgid "a-z" +msgstr "a-z" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "or" +msgstr "of" + +#~ msgid "Users" +#~ msgstr "Gebruikers" + +#, python-format +#~ msgid "" +#~ "This will re-encrypt everything in the vault. Do you want to proceed?" +#~ msgstr "Dit zal alles in de kluis opnieuw versleutelen. Wil je doorgaan?" diff --git a/vault/i18n/vault.pot b/vault/i18n/vault.pot new file mode 100644 index 0000000000..58b4611e0e --- /dev/null +++ b/vault/i18n/vault.pot @@ -0,0 +1,1338 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \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: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "%s '%s' of entry '%s'" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%s (copy)" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "" +"A secure browser context is required. Please switch to https or contact your" +" administrator" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "A-Z" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract_field +msgid "Abstract model to implement basic fields for encryption" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract +msgid "Abstract model to implement general access rights" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__accesses +msgid "Access counter" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__active_key +msgid "Active Key" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_entry +#: model:ir.ui.menu,name:vault.menu_vault_entry +msgid "All Entries" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to export vaults accessible to them" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow all users to import vaults accessible to them" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Allow the usage to share secrets with external users" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_create +msgid "Allow to create in the vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_delete +msgid "Allow to delete a vault" +msgstr "" + +#. module: vault +#: model:res.groups,name:vault.group_vault_export +msgid "Allow to export vaults" +msgstr "" + +#. module: vault +#: model:res.groups,name:vault.group_vault_import +msgid "Allow to import vaults" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_share +msgid "Allow to share a vault with new users" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_write +msgid "Allow to write to the vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_create +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_create +msgid "Allowed Create" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_delete +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_delete +msgid "Allowed Delete" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_read +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_read +msgid "Allowed Read" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_share +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_share +msgid "Allowed Share" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_entry__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_field__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_file__allowed_write +#: model:ir.model.fields,field_description:vault.field_vault_right__allowed_write +msgid "Allowed Write" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "An error occured. Please contact the user or administrator" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By user" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "By vault" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "Cancel" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Cancelled" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Characters:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__child_ids +msgid "Child" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Childs" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_export_wizard +msgid "Close" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__complete_name +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_name +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_name +msgid "Complete Name" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Confirm" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Confirm your password:" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Content" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Copy to clipboard" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_create +msgid "Create" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_uid +#: model:ir.model.fields,field_description:vault.field_vault__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_uid +msgid "Created by" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Created by %(name)s via %(ip)s" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +msgid "Created by %s" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__create_date +#: model:ir.model.fields,field_description:vault.field_vault__create_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__create_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_field__create_date +#: model:ir.model.fields,field_description:vault.field_vault_file__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__create_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_log__create_date +#: model:ir.model.fields,field_description:vault.field_vault_right__create_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__create_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__create_date +msgid "Created on" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__crypted_content +msgid "Crypted Content" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__current +msgid "Current" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Custom JSON format .json" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__content +msgid "Database" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_delete +msgid "Delete" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Discard" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__display_name +#: model:ir.model.fields,field_description:vault.field_vault__display_name +#: model:ir.model.fields,field_description:vault.field_vault_entry__display_name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_field__display_name +#: model:ir.model.fields,field_description:vault.field_vault_file__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__display_name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_log__display_name +#: model:ir.model.fields,field_description:vault.field_vault_right__display_name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__display_name +#: model:ir.model.fields,field_description:vault.field_vault_tag__display_name +msgid "Display Name" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Do you really want to create a new key pair and set it active?" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__content +msgid "Download" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Enter" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Enter your password:" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_open_entries +#: model:ir.model.fields,field_description:vault.field_vault__entry_ids +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__entry_id +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Entries" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_field__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_file__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_log__entry_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__entry_id +msgid "Entry" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_entry +msgid "Entry inside a vault" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "Error" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__expiration +msgid "Expiration" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expired +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Expired" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__expire_date +msgid "Expires on" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_export +msgid "Export Vaults" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Export to file" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_export_wizard +msgid "Export wizard for vaults" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to export keys to object store" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to export the keys to the database" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to import keys from database" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_field +msgid "Field of a vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__field_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__field_ids +msgid "Fields" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_file +msgid "File of a vault" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "File to share:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__filename +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__filename +msgid "Filename" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__file_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__file_ids +msgid "Files" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__fingerprint +msgid "Fingerprint" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Generate" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Generate a new secret:" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_right_overview_search +msgid "Grouped" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Hide" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__id +#: model:ir.model.fields,field_description:vault.field_vault__id +#: model:ir.model.fields,field_description:vault.field_vault_entry__id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_field__id +#: model:ir.model.fields,field_description:vault.field_vault_file__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__id +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__id +#: model:ir.model.fields,field_description:vault.field_vault_log__id +#: model:ir.model.fields,field_description:vault.field_vault_right__id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__id +#: model:ir.model.fields,field_description:vault.field_vault_tag__id +msgid "ID" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__expiration +msgid "If expired the inbox can't be written using the link" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__accesses +msgid "If this is 0 the inbox can't be written using the link" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Import" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__group_vault_import +msgid "Import Vaults" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault.py:0 +#: code:addons/vault/models/vault_entry.py:0 +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Import from file" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard +msgid "Import wizard for vaults" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard_path +msgid "Import wizard path for vaults" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_inbox +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__inbox_id +#: model:ir.ui.menu,name:vault.menu_vault_inbox +msgid "Inbox" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_enabled +msgid "Inbox Enabled" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_link +#: model:ir.model.fields,field_description:vault.field_vault_inbox__inbox_link +msgid "Inbox Link" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__inbox_token +msgid "Inbox Token" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__include_childs +msgid "Include Childs" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "Information" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +msgid "Invalid file to import from" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +msgid "Invalid parameter" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "Invalid token" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "Invalidate private key" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iterations +msgid "Iterations" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__iv +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_field__iv +#: model:ir.model.fields,field_description:vault.field_vault_file__iv +#: model:ir.model.fields,field_description:vault.field_vault_inbox__iv +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__iv +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__iv +msgid "Iv" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "Keepass Database .kdbx" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__key +#: model:ir.model.fields,field_description:vault.field_vault_right__key +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__key +msgid "Key" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/user_menu.esm.js:0 +msgid "Key Management" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key_user +msgid "Key User" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Keyfile:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__keys +msgid "Keys" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_uid +#: model:ir.model.fields,field_description:vault.field_vault__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_field__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_file__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_log__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_right__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_uid +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__write_date +#: model:ir.model.fields,field_description:vault.field_vault__write_date +#: model:ir.model.fields,field_description:vault.field_vault_entry__write_date +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_field__write_date +#: model:ir.model.fields,field_description:vault.field_vault_file__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox__write_date +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_log__write_date +#: model:ir.model.fields,field_description:vault.field_vault_right__write_date +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__write_date +#: model:ir.model.fields,field_description:vault.field_vault_tag__write_date +msgid "Last Updated on" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Length:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_entry__log_ids +#: model:ir.model.fields,field_description:vault.field_vault_inbox__log_ids +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Log" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_log +msgid "Log entry of a vault" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_res_users_keys +msgid "Manage my keys" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__master_key +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_field__master_key +#: model:ir.model.fields,field_description:vault.field_vault_file__master_key +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__master_key +#: model:ir.model.fields,field_description:vault.field_vault_right__master_key +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__master_key +msgid "Master Key" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__message +msgid "Message" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "Missing filename" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Missing password" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__model +msgid "Model" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__module_vault_share +msgid "Module Vault Share" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__name +#: model:ir.model.fields,field_description:vault.field_vault_entry__name +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_field__name +#: model:ir.model.fields,field_description:vault.field_vault_file__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox__name +#: model:ir.model.fields,field_description:vault.field_vault_inbox_log__name +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__name +#: model:ir.model.fields,field_description:vault.field_vault_tag__name +msgid "Name" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Name of your secret:" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +msgid "Neither a secret nor file was given" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New inbox link" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New private key" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "No secret found" +msgstr "" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_inbox_value_check +#: model:ir.model.constraint,message:vault.constraint_vault_send_wizard_value_check +msgid "No value found" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Not Expired" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__note +#: model:ir.model.fields,field_description:vault.field_vault_entry__note +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Note" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__user_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__user_id +msgid "Owner" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__parent_id +msgid "Parent" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__parent_id +msgid "Parent Entry" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Password:" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__path +msgid "Path to import" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_entry__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_field__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_file__perm_user +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_user +msgid "Perm User" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/export.esm.js:0 +#: code:addons/vault/static/src/backend/import.esm.js:0 +msgid "Please enter the password for the database" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +msgid "Please enter the password for the keepass database" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Please enter the password for your private key" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Please enter your password or upload a keyfile:" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "Please specify a name" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__private +msgid "Private" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__public +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__public +msgid "Public" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__public_key +msgid "Public Key" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Re-encrypt" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__reencrypt_required +msgid "Reencrypt Required" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault_right +#: model:ir.model.fields,field_description:vault.field_vault__right_ids +#: model:ir.ui.menu,name:vault.menu_vault_right +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Rights" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__salt +msgid "Salt" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +msgid "Save As..." +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Save in a vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__secret_file +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__secret_file +msgid "Secret File" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__secret_temporary +msgid "Secret Temporary" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret to share:" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Select at least one character set" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "Send" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Send the secret to an user" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_mixin.esm.js:0 +msgid "Send the secret to another user" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_share +msgid "Share" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Show" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "Something went wrong with the encryption" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Special" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_log__state +msgid "State" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_store_wizard +msgid "Store" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_mixin.esm.js:0 +msgid "Store the secret in a vault" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Submit secret" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "Successfully stored" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__tags +msgid "Tags" +msgstr "" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_entry_vault_uuid_uniq +#: model:ir.model.constraint,message:vault.constraint_vault_uuid_uniq +msgid "The UUID must be unique." +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +msgid "The field is empty, there's nothing to save!" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_import_wizard +msgid "The files must end on one of the supported file type:" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "The following entries are broken:" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "The passwords aren't matching" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/abstract_vault.py:0 +msgid "" +"The requested operation can not be completed due to security restrictions." +msgstr "" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +msgid "The tag must be unique!" +msgstr "" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +msgid "The user must be unique" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_inbox__token +msgid "Token" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +msgid "Unsupported file to import" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__url +msgid "Url" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Use password" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_res_users +#: model:ir.model.fields,field_description:vault.field_res_users_key__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__user_id +#: model:ir.model.fields,field_description:vault.field_vault_right__user_id +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__user_id +msgid "User" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_res_users_key +msgid "User data of a vault" +msgstr "" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_inbox__inbox_link +msgid "" +"Using this link you can write to the current inbox. If you want people to " +"create new inboxes you should give them your inbox link from your key " +"management." +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__uuid +#: model:ir.model.fields,field_description:vault.field_vault__uuid +#: model:ir.model.fields,field_description:vault.field_vault_entry__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__uuid +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard_path__uuid +msgid "Uuid" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_field__value +#: model:ir.model.fields,field_description:vault.field_vault_file__value +msgid "Value" +msgstr "" + +#. module: vault +#: model:ir.actions.act_window,name:vault.action_vault +#: model:ir.model,name:vault.model_vault +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_entry__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_export_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_field__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_file__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_import_wizard__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_inbox__user_id +#: model:ir.model.fields,field_description:vault.field_vault_log__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_right__vault_id +#: model:ir.model.fields,field_description:vault.field_vault_store_wizard__vault_id +#: model:ir.ui.menu,name:vault.menu_vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vault" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +msgid "Vault Field" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +msgid "Vault File" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +msgid "Vault Inbox Field" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_file.esm.js:0 +msgid "Vault Inbox File" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__vault_right_ids +msgid "Vault Right" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Vault Share" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +msgid "Vault export file" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox_log +msgid "Vault inbox log" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Vault is not supported" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_right +msgid "Vault rights" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox +msgid "Vault share incoming secrets" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_tag +msgid "Vault tag" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vaults" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Verify" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users_key__version +msgid "Version" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "Warning" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_store_wizard +msgid "Wizard store a shared secret in a vault" +msgstr "" + +#. module: vault +#: model:ir.model,name:vault.model_vault_send_wizard +msgid "Wizard to send another user a secret" +msgstr "" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_write +msgid "Write" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Written by %(name)s via %(ip)s" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "You can not create recursive entries." +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_send_wizard +msgid "" +"You can only send the secret to the user who has generated a key-pair.\n" +" If an user is not showing please ask him to generate these." +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "" +"You will loose access to all vaults and your inbox. Do you want to continue?" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "a-z" +msgstr "" diff --git a/vault/models/__init__.py b/vault/models/__init__.py new file mode 100644 index 0000000000..d9d3e40ebe --- /dev/null +++ b/vault/models/__init__.py @@ -0,0 +1,19 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + abstract_vault, + abstract_vault_field, + res_config_settings, + res_users, + res_users_key, + vault, + vault_entry, + vault_field, + vault_file, + vault_inbox, + vault_inbox_log, + vault_log, + vault_right, + vault_tag, +) diff --git a/vault/models/abstract_vault.py b/vault/models/abstract_vault.py new file mode 100644 index 0000000000..feeb5db787 --- /dev/null +++ b/vault/models/abstract_vault.py @@ -0,0 +1,77 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, api, models +from odoo.exceptions import AccessError + +_logger = logging.getLogger(__name__) + + +class AbstractVault(models.AbstractModel): + """Models must have the following fields: + `perm_user`: The permissions are computed for this user + `allowed_read`: The current user can read from the vault + `allowed_create`: The current user can read from the vault + `allowed_write`: The current user has write access to the vault + `allowed_share`: The current user can share the vault with other users + `allowed_delete`: The current user can delete the vault or entries of it + """ + + _name = "vault.abstract" + _description = "Abstract model to implement general access rights" + + @api.model + def raise_access_error(self): + raise AccessError( + _( + "The requested operation can not be completed due to security " + "restrictions." + ) + ) + + def check_access(self, operation): + super().check_access(operation) + + if not self or self.env.su: + return + + # We have to recompute if the user of the environment changed + if self.env.user != self.mapped("perm_user"): + vault = self if self._name == "vault" else self.mapped("vault_id") + vault._compute_access() + + # Shortcut for vault.right because only the share right is required + if self._name == "vault.right": + if not self.filtered("allowed_share"): + self.raise_access_error() + return + + # Check the operation and matching permissions + if operation == "read" and not self.filtered("allowed_read"): + self.raise_access_error() + + if operation == "create" and not self.filtered("allowed_create"): + self.raise_access_error() + + if operation == "write" and not self.filtered("allowed_write"): + self.raise_access_error() + + if operation == "unlink" and not self.filtered("allowed_delete"): + self.raise_access_error() + + def _log_entry(self, msg, state): + raise NotImplementedError() + + def log_entry(self, msg): + return self._log_entry(msg, None) + + def log_info(self, msg): + return self._log_entry(msg, "info") + + def log_warn(self, msg): + return self._log_entry(msg, "warn") + + def log_error(self, msg): + return self._log_entry(msg, "error") diff --git a/vault/models/abstract_vault_field.py b/vault/models/abstract_vault_field.py new file mode 100644 index 0000000000..21700135b0 --- /dev/null +++ b/vault/models/abstract_vault_field.py @@ -0,0 +1,57 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class AbstractVaultField(models.AbstractModel): + _name = "vault.abstract.field" + _description = "Abstract model to implement basic fields for encryption" + + entry_id = fields.Many2one("vault.entry", ondelete="cascade", required=True) + entry_name = fields.Char(related="entry_id.complete_name") + vault_id = fields.Many2one(related="entry_id.vault_id") + master_key = fields.Char(compute="_compute_master_key", store=False) + + perm_user = fields.Many2one(related="vault_id.perm_user", store=False) + allowed_read = fields.Boolean(related="vault_id.allowed_read", store=False) + allowed_create = fields.Boolean(related="vault_id.allowed_create", store=False) + allowed_write = fields.Boolean(related="vault_id.allowed_write", store=False) + allowed_share = fields.Boolean(related="vault_id.allowed_share", store=False) + allowed_delete = fields.Boolean(related="vault_id.allowed_delete", store=False) + + name = fields.Char(required=True) + iv = fields.Char() + + @api.depends("entry_id.vault_id.master_key") + def _compute_master_key(self): + for rec in self: + rec.master_key = rec.vault_id.master_key + + def log_change(self, action): + if self.env.context.get("vault_skip_log"): + return + + for rec in self: + rec.entry_id.log_info( + f"{action} value {rec.name} of {rec.entry_id.complete_name} " + f"by {self.env.user.display_name}" + ) + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + res.log_change("Created") + return res + + def unlink(self): + self.log_change("Deleted") + return super().unlink() + + def write(self, values): + self.log_change("Changed") + return super().write(values) diff --git a/vault/models/res_config_settings.py b/vault/models/res_config_settings.py new file mode 100644 index 0000000000..105571263f --- /dev/null +++ b/vault/models/res_config_settings.py @@ -0,0 +1,16 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + module_vault_share = fields.Boolean() + group_vault_export = fields.Boolean( + "Export Vaults", implied_group="vault.group_vault_export" + ) + group_vault_import = fields.Boolean( + "Import Vaults", implied_group="vault.group_vault_import" + ) diff --git a/vault/models/res_users.py b/vault/models/res_users.py new file mode 100644 index 0000000000..8c6c75e231 --- /dev/null +++ b/vault/models/res_users.py @@ -0,0 +1,79 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from uuid import uuid4 + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class ResUsers(models.Model): + _inherit = "res.users" + + active_key = fields.Many2one( + "res.users.key", + compute="_compute_active_key", + store=False, + ) + keys = fields.One2many("res.users.key", "user_id", readonly=True) + vault_right_ids = fields.One2many("vault.right", "user_id", readonly=True) + inbox_ids = fields.One2many("vault.inbox", "user_id") + inbox_enabled = fields.Boolean(default=True) + inbox_link = fields.Char(compute="_compute_inbox_link", readonly=True, store=False) + inbox_token = fields.Char(default=lambda self: uuid4(), readonly=True) + + @api.depends("keys", "keys.current") + def _compute_active_key(self): + for rec in self: + keys = rec.sudo().keys.filtered("current") + rec.active_key = keys[0] if keys else None + + @api.depends("inbox_token") + def _compute_inbox_link(self): + base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url") + for rec in self: + rec.inbox_link = f"{base_url}/vault/inbox/{rec.inbox_token}" + + @api.model + def action_get_vault(self): + result = self.env["ir.actions.act_window"]._for_xml_id( + "vault.action_res_users_keys" + ) + result["res_id"] = self.env.uid + return result + + def action_new_inbox_token(self): + self.ensure_one() + self.sudo().inbox_token = uuid4() + return self.action_get_vault() + + def action_invalidate_key(self): + """Disable the current key and remove all accesses to the vaults""" + self.ensure_one() + self.keys.write({"current": False}) + self.vault_right_ids.sudo().unlink() + self.inbox_ids.unlink() + self.env["vault"].search([])._compute_access() + return self.action_get_vault() + + @api.model + def find_user_of_inbox(self, token): + return self.search([("inbox_token", "=", token), ("inbox_enabled", "=", True)]) + + def get_vault_keys(self): + self.ensure_one() + + if not self.active_key: + return {} + + return { + "iterations": self.active_key.iterations, + "iv": self.active_key.iv, + "private": self.active_key.private, + "public": self.active_key.public, + "salt": self.active_key.salt, + "uuid": self.active_key.uuid, + "version": self.active_key.version, + } diff --git a/vault/models/res_users_key.py b/vault/models/res_users_key.py new file mode 100644 index 0000000000..35e3d06f4e --- /dev/null +++ b/vault/models/res_users_key.py @@ -0,0 +1,82 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +import re +from hashlib import sha256 +from uuid import uuid4 + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class ResUsersKey(models.Model): + _name = "res.users.key" + _description = "User data of a vault" + _rec_name = "fingerprint" + _order = "create_date DESC" + + user_id = fields.Many2one("res.users", required=True) + uuid = fields.Char(default=lambda self: uuid4(), required=True, readonly=True) + current = fields.Boolean(default=True, readonly=True) + fingerprint = fields.Char(compute="_compute_fingerprint", store=True) + public = fields.Char(required=True, readonly=True) + salt = fields.Char(required=True, readonly=True) + iv = fields.Char(required=True, readonly=True) + iterations = fields.Integer(required=True, readonly=True) + version = fields.Integer(readonly=True) + # Encrypted with master password of user + private = fields.Char(required=True, readonly=True) + + @api.depends("public") + def _compute_fingerprint(self): + for rec in self: + if rec.public: + hashed = sha256(rec.public.encode()).hexdigest() + rec.fingerprint = ":".join(re.findall(r".{2}", hashed)) + else: + rec.fingerprint = False + + def _prepare_values(self, iterations, iv, private, public, salt, version): + return { + "iterations": iterations, + "iv": iv, + "private": private, + "public": public, + "salt": salt, + "user_id": self.env.uid, + "current": True, + "version": version, + } + + def store(self, iterations, iv, private, public, salt, version): + if not all(isinstance(x, str) and x for x in [public, private, iv, salt]): + raise ValidationError(_("Invalid parameter")) + + if not isinstance(iterations, int) or iterations < 4000: + raise ValidationError(_("Invalid parameter")) + + if not isinstance(version, int): + raise ValidationError(_("Invalid parameter")) + + domain = [ + ("user_id", "=", self.env.uid), + ("private", "=", private), + ] + key = self.search(domain) + if not key: + # Disable all current keys + self.env.user.keys.write({"current": False}) + + rec = self.create( + self._prepare_values(iterations, iv, private, public, salt, version) + ) + return rec.uuid + + return False + + def extract_public_key(self, user): + user = self.sudo().search([("user_id", "=", user), ("current", "=", True)]) + return user.public or None diff --git a/vault/models/vault.py b/vault/models/vault.py new file mode 100644 index 0000000000..59eb5c1698 --- /dev/null +++ b/vault/models/vault.py @@ -0,0 +1,165 @@ +# © 2021-2024 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from uuid import uuid4 + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + + +class Vault(models.Model): + _name = "vault" + _description = "Vault" + _inherit = ["vault.abstract"] + _order = "name" + + user_id = fields.Many2one( + "res.users", + "Owner", + readonly=True, + default=lambda self: self.env.user, + required=True, + ) + right_ids = fields.One2many( + "vault.right", + "vault_id", + "Rights", + default=lambda self: self._get_default_rights(), + ) + entry_ids = fields.One2many("vault.entry", "vault_id", "Entries") + field_ids = fields.One2many("vault.field", "vault_id", "Fields") + file_ids = fields.One2many("vault.file", "vault_id", "Files") + log_ids = fields.One2many("vault.log", "vault_id", "Log", readonly=True) + reencrypt_required = fields.Boolean(default=False) + + # Access control + perm_user = fields.Many2one("res.users", compute="_compute_access", store=False) + allowed_read = fields.Boolean(compute="_compute_access", store=False) + allowed_create = fields.Boolean(compute="_compute_access", store=False) + allowed_share = fields.Boolean(compute="_compute_access", store=False) + allowed_write = fields.Boolean(compute="_compute_access", store=False) + allowed_delete = fields.Boolean(compute="_compute_access", store=False) + + master_key = fields.Char( + compute="_compute_master_key", + inverse="_inverse_master_key", + store=False, + ) + + uuid = fields.Char(default=lambda self: uuid4(), required=True, readonly=True) + name = fields.Char(required=True) + note = fields.Text() + + _sql_constraints = [ + ("uuid_uniq", "UNIQUE(uuid)", "The UUID must be unique."), + ] + + @api.depends("right_ids.user_id") + def _compute_access(self): + user = self.env.user + for rec in self.sudo(): + rec.perm_user = user.id + + if user == rec.user_id: + rec.write( + { + "allowed_create": True, + "allowed_share": True, + "allowed_write": True, + "allowed_delete": True, + "allowed_read": True, + } + ) + continue + + rights = rec.right_ids + rec.allowed_read = user in rights.mapped("user_id") + rec.allowed_create = user in rights.filtered("perm_create").mapped( + "user_id" + ) + rec.allowed_share = user in rights.filtered("perm_share").mapped("user_id") + rec.allowed_write = user in rights.filtered("perm_write").mapped("user_id") + rec.allowed_delete = user in rights.filtered("perm_delete").mapped( + "user_id" + ) + + @api.depends("right_ids.key") + def _compute_master_key(self): + domain = [("user_id", "=", self.env.uid)] + for rec in self: + rights = rec.right_ids.filtered_domain(domain) + rec.master_key = rights[0].key if rights else False + + def _inverse_master_key(self): + domain = [("user_id", "=", self.env.uid)] + for rec in self: + rights = rec.right_ids.filtered_domain(domain) + if rights and not rights.key: + rights.key = rec.master_key + + def _get_default_rights(self): + return [ + ( + 0, + 0, + { + "user_id": self.env.uid, + "perm_create": True, + "perm_write": True, + "perm_delete": True, + "perm_share": True, + }, + ) + ] + + def _log_entry(self, msg, state): + self.ensure_one() + return ( + self.env["vault.log"] + .sudo() + .create( + { + "vault_id": self.id, + "user_id": self.env.uid, + "message": msg, + "state": state, + } + ) + ) + + def share_public_keys(self): + self.ensure_one() + result = [] + for right in self.right_ids: + result.append({"user": right.user_id.id, "public": right.public_key}) + return result + + def action_open_import_wizard(self): + self.ensure_one() + wizard = self.env.ref("vault.view_vault_import_wizard") + return { + "name": _("Import from file"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "vault.import.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": {"default_vault_id": self.id}, + } + + def action_open_export_wizard(self): + self.ensure_one() + wizard = self.env.ref("vault.view_vault_export_wizard") + return { + "name": _("Export to file"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "vault.export.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": {"default_vault_id": self.id}, + } diff --git a/vault/models/vault_entry.py b/vault/models/vault_entry.py new file mode 100644 index 0000000000..8fee29213b --- /dev/null +++ b/vault/models/vault_entry.py @@ -0,0 +1,215 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# Copyright 2022 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime +from uuid import uuid4 + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class VaultEntry(models.Model): + _name = "vault.entry" + _description = "Entry inside a vault" + _inherit = ["vault.abstract"] + _order = "complete_name" + _rec_name = "complete_name" + + parent_id = fields.Many2one( + "vault.entry", + "Parent", + ondelete="cascade", + domain="[('vault_id', '=', vault_id)]", + ) + child_ids = fields.One2many("vault.entry", "parent_id", "Child") + + vault_id = fields.Many2one("vault", "Vault", ondelete="cascade", required=True) + user_id = fields.Many2one(related="vault_id.user_id") + field_ids = fields.One2many("vault.field", "entry_id", "Fields") + file_ids = fields.One2many("vault.file", "entry_id", "Files") + log_ids = fields.One2many("vault.log", "entry_id", "Log", readonly=True) + + perm_user = fields.Many2one(related="vault_id.perm_user", store=False) + allowed_read = fields.Boolean(related="vault_id.allowed_read", store=False) + allowed_create = fields.Boolean(related="vault_id.allowed_create", store=False) + allowed_share = fields.Boolean(related="vault_id.allowed_share", store=False) + allowed_write = fields.Boolean(related="vault_id.allowed_write", store=False) + allowed_delete = fields.Boolean(related="vault_id.allowed_delete", store=False) + + complete_name = fields.Char( + compute="_compute_complete_name", + store=True, + readonly=True, + recursive=True, + ) + uuid = fields.Char(default=lambda self: uuid4(), required=True, copy=False) + name = fields.Char(required=True) + url = fields.Char() + note = fields.Text() + tags = fields.Many2many("vault.tag") + expire_date = fields.Datetime("Expires on", default=False) + expired = fields.Boolean( + compute="_compute_expired", + search="_search_expired", + store=False, + ) + + _sql_constraints = [ + ("vault_uuid_uniq", "UNIQUE(vault_id, uuid)", "The UUID must be unique."), + ] + + @api.constrains("parent_id") + def _check_parent_id(self): + if self._has_cycle(): + raise ValidationError(_("You can not create recursive entries.")) + + @api.depends("name", "parent_id.complete_name") + def _compute_complete_name(self): + for rec in self: + if rec.parent_id: + rec.complete_name = f"{rec.parent_id.complete_name} / {rec.name}" + else: + rec.complete_name = rec.name + + @api.model + def search_panel_select_range(self, field_name, **kwargs): + """We add the following contexts related to searchpanel: + - entry_short_name: Show just the name instead of full path. + - from_search_panel: It will be used to overwrite domain. + Remove the limit of records (default is 200). + """ + return super( + VaultEntry, + self.with_context(from_search_panel=True, entry_short_name=True), + ).search_panel_select_range(field_name, **kwargs) + + def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None): + """Changes related to searchpanel: + - Add a domain to only show records with children. + """ + domain = domain if domain else [] + if self.env.context.get("from_search_panel"): + domain += [("child_ids", "!=", False)] + return super().search_read( + domain=domain, fields=fields, offset=offset, limit=limit, order=order + ) + + def copy_data(self, default=None): + self.ensure_one() + + if default is None: + default = {} + + if "name" not in default: + default["name"] = _("%s (copy)", self.name) + + if "field_ids" not in default: + default["field_ids"] = [ + (0, 0, field.copy_data()[0]) for field in self.field_ids + ] + if "file_ids" not in default: + default["file_ids"] = [ + (0, 0, field.copy_data()[0]) for field in self.file_ids + ] + return super().copy_data(default) + + @api.depends("name", "complete_name") + def _compute_display_name(self): + if not self.env.context.get("entry_short_name", False): + return super()._compute_display_name() + for record in self: + record.display_name = record.name + + @api.depends("expire_date") + def _compute_expired(self): + now = datetime.now() + for rec in self: + rec.expired = rec.expire_date and now > rec.expire_date + + def _search_expired(self, operator, value): + if (operator not in ["=", "!="]) or (value not in [True, False]): + return [] + + if (operator, value) in [("=", True), ("!=", False)]: + return [("expire_date", "<", datetime.now())] + + return ["|", ("expire_date", ">=", datetime.now()), ("expire_date", "=", False)] + + def log_change(self, action): + if self.env.context.get("vault_skip_log"): + return + + for rec in self: + rec.log_info( + _("%(action)s entry %(name)s by %(user)s") + % { + "action": action, + "name": rec.complete_name, + "user": rec.env.user.display_name, + } + ) + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + res.log_change("Created") + return res + + def unlink(self): + self.log_change("Deleted") + + return super().unlink() + + def _log_entry(self, msg, state): + self.ensure_one() + return ( + self.env["vault.log"] + .sudo() + .create( + { + "vault_id": self.vault_id.id, + "entry_id": self.id, + "user_id": self.env.uid, + "message": msg, + "state": state, + } + ) + ) + + def action_open_import_wizard(self): + self.ensure_one() + wizard = self.env.ref("vault.view_vault_import_wizard") + return { + "name": _("Import from file"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "vault.import.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": { + "default_vault_id": self.vault_id.id, + "default_parent_id": self.id, + }, + } + + def action_open_export_wizard(self): + self.ensure_one() + wizard = self.env.ref("vault.view_vault_export_wizard") + return { + "name": _("Export to file"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "vault.export.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": { + "default_vault_id": self.vault_id.id, + "default_entry_id": self.id, + }, + } diff --git a/vault/models/vault_field.py b/vault/models/vault_field.py new file mode 100644 index 0000000000..bb056e1817 --- /dev/null +++ b/vault/models/vault_field.py @@ -0,0 +1,17 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class VaultField(models.Model): + _name = "vault.field" + _description = "Field of a vault" + _order = "name" + _inherit = ["vault.abstract.field", "vault.abstract"] + + value = fields.Char(required=True) diff --git a/vault/models/vault_file.py b/vault/models/vault_file.py new file mode 100644 index 0000000000..ba7d7d0d4c --- /dev/null +++ b/vault/models/vault_file.py @@ -0,0 +1,23 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultFile(models.Model): + _name = "vault.file" + _description = "File of a vault" + _order = "name" + _inherit = ["vault.abstract.field", "vault.abstract"] + + value = fields.Binary(attachment=False) + + @api.model + def search_read(self, *args, **kwargs): + if self.env.context.get("vault_reencrypt"): + self = self.with_context(bin_size=False) + return super().search_read(*args, **kwargs) diff --git a/vault/models/vault_inbox.py b/vault/models/vault_inbox.py new file mode 100644 index 0000000000..cdae12e616 --- /dev/null +++ b/vault/models/vault_inbox.py @@ -0,0 +1,114 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +from uuid import uuid4 + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultInbox(models.Model): + _name = "vault.inbox" + _description = "Vault share incoming secrets" + + token = fields.Char(default=lambda self: uuid4(), readonly=True, copy=False) + inbox_link = fields.Char( + compute="_compute_inbox_link", + readonly=True, + help="Using this link you can write to the current inbox. If you want people " + "to create new inboxes you should give them your inbox link from your key " + "management.", + ) + user_id = fields.Many2one( + "res.users", + "Vault", + required=True, + ) + name = fields.Char(required=True) + secret = fields.Char(readonly=True) + filename = fields.Char() + secret_file = fields.Binary(attachment=False, readonly=True) + key = fields.Char(required=True) + iv = fields.Char(required=True) + accesses = fields.Integer( + "Access counter", + default=1, + help="If this is 0 the inbox can't be written using the link", + ) + expiration = fields.Datetime( + default=lambda self: datetime.now() + timedelta(days=7), + help="If expired the inbox can't be written using the link", + ) + log_ids = fields.One2many("vault.inbox.log", "inbox_id", "Log", readonly=True) + + _sql_constraints = [ + ( + "value_check", + "CHECK(secret IS NOT NULL OR secret_file IS NOT NULL)", + "No value found", + ), + ] + + @api.depends("token") + def _compute_inbox_link(self): + base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url") + for rec in self: + rec.inbox_link = f"{base_url}/vault/inbox/{rec.token}" + + def read(self, *args, **kwargs): + # Always load the binary instead of the size + return super(VaultInbox, self.with_context(bin_size=False)).read( + *args, **kwargs + ) + + @api.model + def find_inbox(self, token): + return self.search([("token", "=", token)]) + + def store_in_inbox( + self, + name, + secret, + secret_file, + iv, + key, + user, + filename, + ip=None, + ): + log_info = {"name": user.name, "ip": ip or "n/a"} + if len(self) == 0: + log = _("Created by %(name)s via %(ip)s") % log_info + return self.create( + { + "name": name, + "accesses": 0, + "iv": iv, + "key": key, + "secret": secret or None, + "secret_file": secret_file or None, + "filename": filename, + "user_id": user.id, + "log_ids": [(0, 0, {"name": log})], + } + ) + + self.ensure_one() + if self.accesses > 0 and datetime.now() < self.expiration: + log = _("Written by %(name)s via %(ip)s") % log_info + + self.write( + { + "accesses": self.accesses - 1, + "iv": iv, + "key": key, + "secret": secret or None, + "secret_file": secret_file or None, + "filename": filename, + "log_ids": [(0, 0, {"name": log})], + } + ) + return self diff --git a/vault/models/vault_inbox_log.py b/vault/models/vault_inbox_log.py new file mode 100644 index 0000000000..91ce93b6ff --- /dev/null +++ b/vault/models/vault_inbox_log.py @@ -0,0 +1,22 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class VaultInboxLog(models.Model): + _name = "vault.inbox.log" + _description = "Vault inbox log" + _order = "create_date DESC" + + inbox_id = fields.Many2one( + "vault.inbox", + ondelete="cascade", + readonly=True, + required=True, + ) + name = fields.Char(readonly=True) diff --git a/vault/models/vault_log.py b/vault/models/vault_log.py new file mode 100644 index 0000000000..b65eb38aac --- /dev/null +++ b/vault/models/vault_log.py @@ -0,0 +1,46 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultLog(models.Model): + _name = "vault.log" + _description = "Log entry of a vault" + _order = "create_date DESC" + _rec_name = "message" + + vault_id = fields.Many2one( + "vault", + "Vault", + ondelete="cascade", + required=True, + readonly=True, + ) + entry_id = fields.Many2one( + "vault.entry", + "Entry", + ondelete="cascade", + readonly=True, + ) + user_id = fields.Many2one("res.users", "User", required=True, readonly=True) + state = fields.Selection(lambda self: self._get_log_state(), readonly=True) + message = fields.Char(readonly=True, required=True) + + def _get_log_state(self): + return [ + ("info", _("Information")), + ("warn", _("Warning")), + ("error", _("Error")), + ] + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + if not self.env.context.get("skip_log", False): + _logger.info("Vault log: %s", res.message) + return res diff --git a/vault/models/vault_right.py b/vault/models/vault_right.py new file mode 100644 index 0000000000..f1ec6177f8 --- /dev/null +++ b/vault/models/vault_right.py @@ -0,0 +1,110 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class VaultRight(models.Model): + _name = "vault.right" + _description = "Vault rights" + _inherit = ["vault.abstract"] + _order = "user_id" + + vault_id = fields.Many2one( + "vault", + "Vault", + readonly=True, + required=True, + ondelete="cascade", + ) + master_key = fields.Char(related="vault_id.master_key", readonly=True, store=False) + user_id = fields.Many2one( + "res.users", + "User", + domain=[("keys", "!=", False)], + required=True, + ) + public_key = fields.Char(compute="_compute_public_key", readonly=True, store=False) + perm_create = fields.Boolean( + "Create", + default=lambda self: self._get_is_owner(), + help="Allow to create in the vault", + ) + perm_write = fields.Boolean( + "Write", + default=lambda self: self._get_is_owner(), + help="Allow to write to the vault", + ) + perm_share = fields.Boolean( + "Share", + default=lambda self: self._get_is_owner(), + help="Allow to share a vault with new users", + ) + perm_delete = fields.Boolean( + "Delete", + default=lambda self: self._get_is_owner(), + help="Allow to delete a vault", + ) + + perm_user = fields.Many2one(related="vault_id.perm_user", store=False) + allowed_read = fields.Boolean(related="vault_id.allowed_read", store=False) + allowed_create = fields.Boolean(related="vault_id.allowed_create", store=False) + allowed_write = fields.Boolean(related="vault_id.allowed_write", store=False) + allowed_share = fields.Boolean(related="vault_id.allowed_share", store=False) + allowed_delete = fields.Boolean(related="vault_id.allowed_delete", store=False) + + # Encrypted with the public key of the user + key = fields.Char() + + _sql_constraints = ( + ("user_uniq", "UNIQUE(user_id, vault_id)", "The user must be unique"), + ) + + def _get_is_owner(self): + return self.env.user == self.vault_id.user_id + + @api.depends("user_id") + def _compute_public_key(self): + for rec in self: + rec.public_key = rec.user_id.active_key.public + + def log_access(self): + for rec in self: + rights = ", ".join( + sorted( + ["read"] + + [ + right + for right in ["create", "write", "share", "delete"] + if getattr(rec, f"perm_{right}", False) + ] + ) + ) + + rec.vault_id.log_info( + f"Grant access to user {rec.user_id.display_name}: {rights}" + ) + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + if not res.allowed_share and not res.env.su: + self.raise_access_error() + + res.log_access() + return res + + def write(self, values): + res = super().write(values) + perms = ["perm_write", "perm_delete", "perm_share", "perm_create"] + if any(x in values for x in perms): + self.log_access() + + return res + + def unlink(self): + for rec in self: + rec.vault_id.log_info(f"Removed user {self.user_id.display_name}") + rec.vault_id.reencrypt_required = True + + return super().unlink() diff --git a/vault/models/vault_tag.py b/vault/models/vault_tag.py new file mode 100644 index 0000000000..aad7397832 --- /dev/null +++ b/vault/models/vault_tag.py @@ -0,0 +1,16 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class VaultTag(models.Model): + _name = "vault.tag" + _description = "Vault tag" + _order = "name" + + name = fields.Char(required=True) + + _sql_constraints = [ + ("name_uniq", "unique(name)", "The tag must be unique!"), + ] diff --git a/vault/pyproject.toml b/vault/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/vault/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/vault/readme/CONTRIBUTORS.md b/vault/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..3149d3d99b --- /dev/null +++ b/vault/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Florian Kantelberg \ +- [Tecnativa](https://www.tecnativa.com): + - Carlos Roca diff --git a/vault/readme/DESCRIPTION.md b/vault/readme/DESCRIPTION.md new file mode 100644 index 0000000000..32eb5769c4 --- /dev/null +++ b/vault/readme/DESCRIPTION.md @@ -0,0 +1,19 @@ +This module implements a vault for secrets and files using +end-to-end-encryption. The encryption and decryption happens in the +browser using a vault specific shared master key. The master keys are +encrypted using asymmetrically. For this the user has to enter a second +password on the first login or if he needs to access data in a vault. +The asymmetric keys are stored for a certain time in the browser +storage. + +The server can never access the secrets with the information available. +Only people registered in the vault can decrypt or encrypt values in a +vault. The meta data isn't encrypted to be able to search/filter for +entries more easily. + +This modules requires a secure context for the browser to work properly +and therefore HTTPS support is required. + +The [vault-recovery](https://github.com/fkantelberg/vault-recovery) +project focuses on disaster recovery in case of an incident to recover +secrets from old database backups or old exports. diff --git a/vault/readme/ROADMAP.md b/vault/readme/ROADMAP.md new file mode 100644 index 0000000000..9101466441 --- /dev/null +++ b/vault/readme/ROADMAP.md @@ -0,0 +1,15 @@ +- Field and file history for restoration +- Import improvement + +> - Support challenge-response/FIDO2 +> - Support for argon2 and kdbx v4 + +- When changing an entry from one vault to another existing vault, the + values added on this entry cannot be accessed, so the field vault is + going to be readonly when it is defined. + + If you want to move entries between vaults you can use the export -\> + import option. + +- HTTPS or localhost (secure browser context) is required for the client + side encryption diff --git a/vault/security/ir.model.access.csv b/vault/security/ir.model.access.csv new file mode 100644 index 0000000000..0863b98aa8 --- /dev/null +++ b/vault/security/ir.model.access.csv @@ -0,0 +1,16 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_vault,access_vault,model_vault,base.group_user,1,1,1,1 +access_vault_entry,access_vault_entry,model_vault_entry,base.group_user,1,1,1,1 +access_vault_export_wizard,access_vault_export_wizard,model_vault_export_wizard,base.group_user,1,1,1,1 +access_vault_field,access_vault_field,model_vault_field,base.group_user,1,1,1,1 +access_vault_file,access_vault_file,model_vault_file,base.group_user,1,1,1,1 +access_vault_import_wizard,access_vault_import_wizard,model_vault_import_wizard,base.group_user,1,1,1,1 +access_vault_import_wizard_path,access_vault_import_wizard_path,model_vault_import_wizard_path,base.group_user,1,1,1,1 +access_vault_inbox,access_vault_inbox,model_vault_inbox,base.group_user,1,1,1,1 +access_vault_inbox_log,access_vault_inbox_log,model_vault_inbox_log,base.group_user,1,1,1,1 +access_vault_log,access_vault_log,model_vault_log,base.group_user,1,0,0,0 +access_vault_right,access_vault_right,model_vault_right,base.group_user,1,1,1,1 +access_vault_send_wizard,access_vault_send_wizard,model_vault_send_wizard,base.group_user,1,1,1,1 +access_vault_store_wizard,access_vault_store_wizard,model_vault_store_wizard,base.group_user,1,1,1,1 +access_vault_tag,access_vault_tag,model_vault_tag,base.group_user,1,1,1,1 +access_vault_users_key,access_res_users_key,model_res_users_key,base.group_user,1,1,1,1 diff --git a/vault/security/ir_rule.xml b/vault/security/ir_rule.xml new file mode 100644 index 0000000000..9ffb2a1486 --- /dev/null +++ b/vault/security/ir_rule.xml @@ -0,0 +1,111 @@ + + + + vault.access.default + + ['|', ('user_id', '=', user.id), ('right_ids.user_id', '=', user.id)] + + + + + + + + + vault.log.access.read + + ['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)] + + + + + + + + + vault.entry.access.default + + ['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)] + + + + + + + + + vault.field.access.default + + ['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)] + + + + + + + + + vault.file.access.default + + ['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)] + + + + + + + + + res.users.key.access.default + + [('user_id', '=', user.id)] + + + + + + + + + vault.inbox.access.owner + + [('user_id', '=', user.id)] + + + + + + + + vault.right.access.default + + [('vault_id.right_ids.user_id', '=', user.id)] + + + + + + + + + vault.inbox.log.access.owner + + [('inbox_id.user_id', '=', user.id)] + + + + + + diff --git a/vault/security/vault_security.xml b/vault/security/vault_security.xml new file mode 100644 index 0000000000..a810f29538 --- /dev/null +++ b/vault/security/vault_security.xml @@ -0,0 +1,13 @@ + + + + Allow to export vaults + + + + + Allow to import vaults + + + + diff --git a/vault/static/description/icon.png b/vault/static/description/icon.png new file mode 100644 index 0000000000..d79aa6f575 Binary files /dev/null and b/vault/static/description/icon.png differ diff --git a/vault/static/description/index.html b/vault/static/description/index.html new file mode 100644 index 0000000000..cc2487ef98 --- /dev/null +++ b/vault/static/description/index.html @@ -0,0 +1,474 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Vault

+ +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

This module implements a vault for secrets and files using +end-to-end-encryption. The encryption and decryption happens in the +browser using a vault specific shared master key. The master keys are +encrypted using asymmetrically. For this the user has to enter a second +password on the first login or if he needs to access data in a vault. +The asymmetric keys are stored for a certain time in the browser +storage.

+

The server can never access the secrets with the information available. +Only people registered in the vault can decrypt or encrypt values in a +vault. The meta data isn’t encrypted to be able to search/filter for +entries more easily.

+

This modules requires a secure context for the browser to work properly +and therefore HTTPS support is required.

+

The vault-recovery +project focuses on disaster recovery in case of an incident to recover +secrets from old database backups or old exports.

+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Field and file history for restoration
  • +
  • Import improvement
  • +
+ +
+
    +
  • Support challenge-response/FIDO2
  • +
  • Support for argon2 and kdbx v4
  • +
+
+
    +
  • When changing an entry from one vault to another existing vault, the +values added on this entry cannot be accessed, so the field vault is +going to be readonly when it is defined.

    +

    If you want to move entries between vaults you can use the export -> +import option.

    +
  • +
  • HTTPS or localhost (secure browser context) is required for the client +side encryption

    +
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • initOS GmbH
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/vault/static/lib/kdbxweb/kdbxweb.min.js b/vault/static/lib/kdbxweb/kdbxweb.min.js new file mode 100644 index 0000000000..eee616aa20 --- /dev/null +++ b/vault/static/lib/kdbxweb/kdbxweb.min.js @@ -0,0 +1,2 @@ +/*! kdbxweb v1.10.0, (c) 2020 Antelle, opensource.org/licenses/MIT */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("crypto"),require("xmldom")):"function"==typeof define&&define.amd?define(["crypto","xmldom"],t):"object"==typeof exports?exports.kdbxweb=t(require("crypto"),require("xmldom")):e.kdbxweb=t(e.crypto,e.xmldom)}(this,(function(e,t){return function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=29)}([function(e,t,r){"use strict";(function(t){var i=t.TextEncoder,n=t.TextDecoder;if(!i||!n){var o=r(40);i=o.TextEncoder,n=o.TextDecoder}var a=new i,s=new n;e.exports.arrayBufferEquals=function(e,t){if(e.byteLength!==t.byteLength)return!1;for(var r=new Uint8Array(e),i=new Uint8Array(t),n=0,o=r.length;n0;){var r=e%65536;r=r>0?r:65536;var i=new Uint8Array(r);s.getRandomValues(i),e-=r,t.set(i,e)}return t}(e);if(h)return new Uint8Array(h.randomBytes(e));throw new n(o.ErrorCodes.NotImplemented,"Random not implemented")},e.exports.createAesCbc=function(){if(d)return new u;if(h)return new l;throw new n(o.ErrorCodes.NotImplemented,"AES-CBC not implemented")},e.exports.chacha20=function(e,t,r){return Promise.resolve().then((function(){var n=new a(new Uint8Array(t),new Uint8Array(r));return i.arrayToBuffer(n.encrypt(new Uint8Array(e)))}))},e.exports.argon2=function(e,t,r,i,a,s,d,h){return Promise.reject(new n(o.ErrorCodes.NotImplemented,"Argon2 not implemented"))},e.exports.configure=function(e,t,r){d=e,s=t,h=r}}).call(this,r(13))},function(e,t,r){"use strict";(function(t){var i=r(2),n=r(1),o=r(6),a=r(7),s=r(9),d=r(0),h=r(8),u=r(16),l=/\.\d\d\d/,c=t.DOMParser?t:r(44),f=t.DOMParser?void 0:{errorHandler:{error:function(e){throw e},fatalError:function(e){throw e}}},p=62135596800;function m(e){var t,r=f?new c.DOMParser(f):new c.DOMParser;try{t=r.parseFromString(e,"application/xml")}catch(e){throw new i(n.ErrorCodes.FileCorrupt,"bad xml: "+e.message)}if(!t.documentElement)throw new i(n.ErrorCodes.FileCorrupt,"bad xml");var o=t.getElementsByTagName("parsererror")[0];if(o)throw new i(n.ErrorCodes.FileCorrupt,"bad xml: "+o.textContent);return t}function y(e,t){var r=e.childNodes.length;if(0!==r){for(var i,n="\n"+" ".repeat(t),o=t>0?"\n"+" ".repeat(t-1):"",a=e.ownerDocument||e,s=[],d=0;d0){var l=a.createTextNode(o);e.appendChild(l)}y(i,t+1)}}}function g(e){if(e&&e.childNodes)return e.protectedValue?e.protectedValue.text:e.textContent}function _(e,t){e.textContent=t||""}function b(e){var t=g(e);return t?d.arrayToBuffer(d.base64ToBytes(t)):void 0}function v(e,t){"string"==typeof t&&(t=d.base64ToBytes(t)),_(e,t?d.bytesToBase64(d.arrayToBuffer(t)):void 0)}function w(e){switch(e&&e.toLowerCase&&e.toLowerCase()){case"true":return!0;case"false":return!1;case"null":return null}}function k(e,t){t(e);for(var r=0,i=e.childNodes,n=i.length;r)<'+e+"/>")},e.exports.getChildNode=function(e,t,r){if(e&&e.childNodes)for(var o=0,a=e.childNodes,s=a.length;o0)return new Date(t);var r=new DataView(d.arrayToBuffer(d.base64ToBytes(t))),i=new h(r.getUint32(0,!0),r.getUint32(4,!0)).value;return new Date(1e3*(i-p))}},e.exports.setDate=function(e,t,r){if(t)if(r){var i=Math.floor(t.getTime()/1e3)+p,n=new DataView(new ArrayBuffer(8)),o=h.from(i);n.setUint32(0,o.lo,!0),n.setUint32(4,o.hi,!0),_(e,d.bytesToBase64(n.buffer))}else _(e,t.toISOString().replace(l,""));else _(e,"")},e.exports.getNumber=function(e){var t=g(e);return t?+t:void 0},e.exports.setNumber=function(e,t){_(e,"number"!=typeof t||isNaN(t)?void 0:t.toString())},e.exports.getBoolean=function(e){var t=g(e);return t?w(t):void 0},e.exports.setBoolean=function(e,t){_(e,void 0===t?"":null===t?"null":t?"True":"False")},e.exports.strToBoolean=w,e.exports.getUuid=function(e){var t=b(e);return t?new a(t):void 0},e.exports.setUuid=function(e,t){v(e,t instanceof a?t.toBytes():t)},e.exports.getProtectedText=function(e){return e.protectedValue||e.textContent},e.exports.setProtectedText=function(e,t){t instanceof s?(e.protectedValue=t,e.setAttribute(o.Attr.Protected,"True")):_(e,t)},e.exports.getProtectedBinary=function(e){if(e.protectedValue)return e.protectedValue;var t=e.textContent,r=e.getAttribute(o.Attr.Ref);if(r)return{ref:r};if(t){var i=w(e.getAttribute(o.Attr.Compressed)),n=d.base64ToBytes(t);return i&&(n=u.ungzip(n)),d.arrayToBuffer(n)}},e.exports.setProtectedBinary=function(e,t){t instanceof s?(e.protectedValue=t,e.setAttribute(o.Attr.Protected,"True")):t&&t.ref?e.setAttribute(o.Attr.Ref,t.ref):v(e,t)},e.exports.setProtectedValues=function(e,t){k(e,(function(e){if(w(e.getAttribute(o.Attr.Protected)))try{var r=d.arrayToBuffer(d.base64ToBytes(e.textContent));if(r.byteLength){var a=t.getSalt(r.byteLength);e.protectedValue=new s(r,a)}}catch(t){throw new i(n.ErrorCodes.FileCorrupt,"bad protected value at line "+e.lineNumber+": "+t)}}))},e.exports.updateProtectedValuesSalt=function(e,t){k(e,(function(e){if(w(e.getAttribute(o.Attr.Protected))&&e.protectedValue){var r=t.getSalt(e.protectedValue.byteLength);e.protectedValue.setSalt(r),e.textContent=e.protectedValue.toString()}}))},e.exports.unprotectValues=function(e){k(e,(function(e){w(e.getAttribute(o.Attr.Protected))&&e.protectedValue&&(e.removeAttribute(o.Attr.Protected),e.setAttribute(o.Attr.ProtectedInMemPlainXml,"True"),e.textContent=e.protectedValue.getText())}))},e.exports.protectUnprotectedValues=function(e){k(e,(function(e){w(e.getAttribute(o.Attr.ProtectedInMemPlainXml))&&e.protectedValue&&(e.removeAttribute(o.Attr.ProtectedInMemPlainXml),e.setAttribute(o.Attr.Protected,"True"),e.textContent=e.protectedValue.toString())}))},e.exports.protectPlainValues=function(e){k(e,(function(e){w(e.getAttribute(o.Attr.ProtectedInMemPlainXml))&&(e.protectedValue=s.fromString(e.textContent),e.textContent=e.protectedValue.toString(),e.removeAttribute(o.Attr.ProtectedInMemPlainXml),e.setAttribute(o.Attr.Protected,"True"))}))}}).call(this,r(13))},function(e,t,r){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;t.assign=function(e){for(var t=Array.prototype.slice.call(arguments,1);t.length;){var r=t.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var i in r)r.hasOwnProperty(i)&&(e[i]=r[i])}}return e},t.shrinkBuf=function(e,t){return e.length===t?e:e.subarray?e.subarray(0,t):(e.length=t,e)};var n={arraySet:function(e,t,r,i,n){if(t.subarray&&e.subarray)e.set(t.subarray(r,r+i),n);else for(var o=0;o=2097152)throw new Error("too large number");return 4294967296*this.hi+this.lo}return this.lo}}),i.prototype.valueOf=function(){return this.value},i.from=function(e){if(e>9007199254740991)throw new Error("too large number");var t=e>>>0;return new i(t,(e-t)/4294967296>>>0)},e.exports=i},function(e,t,r){"use strict";var i=r(0),n=r(3),o=r(10),a=function(e,t){Object.defineProperty(this,"_value",{value:new Uint8Array(e)}),Object.defineProperty(this,"_salt",{value:new Uint8Array(t)})};a.prototype.toString=function(){return i.bytesToBase64(this._value)},a.fromString=function(e){for(var t=i.stringToBytes(e),r=o.getBytes(t.length),n=0,s=t.length;n=0;i--)r[i]=e[i]^t[i];return r},a.prototype.setSalt=function(e){for(var t=new Uint8Array(e),r=this._value,i=this._salt,n=0,o=r.length;n=0;--i)t[i]^=r[i];return t}},function(e,t,r){"use strict";function i(e){this._arrayBuffer=e||new ArrayBuffer(1024),this._dataView=new DataView(this._arrayBuffer),this._pos=0,this._canExpand=!e}["Int","Uint","Float"].forEach((function(e){("Float"===e?[4,8]:[1,2,4]).forEach((function(t){var r="get"+e+8*t;i.prototype[r]=function(e){var i=this._dataView[r].call(this._dataView,this._pos,e);return this._pos+=t,i};var n="set"+e+8*t;i.prototype[n]=function(e,r){this._checkCapacity(t),this._dataView[n].call(this._dataView,this._pos,e,r),this._pos+=t}}))})),i.prototype.getUint64=function(e){var t=this.getUint32(e),r=this.getUint32(e);return e?r*=4294967296:t*=4294967296,t+r},i.prototype.setUint64=function(e,t){t?(this.setUint32(4294967295&e,!0),this.setUint32(Math.floor(e/4294967296),!0)):(this._checkCapacity(8),this.setUint32(Math.floor(e/4294967296),!1),this.setUint32(4294967295&e,!1))},i.prototype.readBytes=function(e){var t=this._arrayBuffer.slice(this._pos,this._pos+e);return this._pos+=e,t},i.prototype.readBytesToEnd=function(){var e=this._arrayBuffer.byteLength-this._pos;return this.readBytes(e)},i.prototype.readBytesNoAdvance=function(e,t){return this._arrayBuffer.slice(e,t)},i.prototype.writeBytes=function(e){e instanceof ArrayBuffer&&(e=new Uint8Array(e)),this._checkCapacity(e.length),new Uint8Array(this._arrayBuffer).set(e,this._pos),this._pos+=e.length},i.prototype.getWrittenBytes=function(){return this._arrayBuffer.slice(0,this._pos)},i.prototype._checkCapacity=function(e){var t=this._arrayBuffer.byteLength-this._pos;if(this._canExpand&&t1)throw new i(n.ErrorCodes.InvalidVersion)},d.prototype._readItem=function(e){var t=e.getUint8();if(!t)return!1;var r=e.getInt32(!0);if(r<=0)throw new i(n.ErrorCodes.FileCorrupt,"bad key length");var d,h=o.bytesToString(e.readBytes(r)),u=e.getInt32(!0);if(u<0)throw new i(n.ErrorCodes.FileCorrupt,"bad value length");switch(t){case s.UInt32:if(4!==u)throw new i(n.ErrorCodes.FileCorrupt,"bad uint32");d=e.getUint32(!0);break;case s.UInt64:if(8!==u)throw new i(n.ErrorCodes.FileCorrupt,"bad uint64");var l=e.getUint32(!0),c=e.getUint32(!0);d=new a(l,c);break;case s.Bool:if(1!==u)throw new i(n.ErrorCodes.FileCorrupt,"bad bool");d=0!==e.getUint8();break;case s.Int32:if(4!==u)throw new i(n.ErrorCodes.FileCorrupt,"bad int32");d=e.getInt32(!0);break;case s.Int64:if(8!==u)throw new i(n.ErrorCodes.FileCorrupt,"bad int64");var f=e.getUint32(!0),p=e.getUint32(!0);d=new a(f,p);break;case s.String:d=o.bytesToString(e.readBytes(u));break;case s.Bytes:d=e.readBytes(u);break;default:throw new i(n.ErrorCodes.FileCorrupt,"bad value type: "+t)}return{key:h,type:t,value:d}},d.prototype.write=function(e){this._writeVersion(e),Object.keys(this._items).forEach((function(t){this._writeItem(e,this._items[t])}),this),e.setUint8(0)},d.prototype._writeVersion=function(e){e.setUint16(256,!0)},d.prototype._writeItem=function(e,t){e.setUint8(t.type);var r=o.stringToBytes(t.key);switch(e.setInt32(r.length,!0),e.writeBytes(r),t.type){case s.UInt32:e.setInt32(4,!0),e.setUint32(t.value,!0);break;case s.UInt64:e.setInt32(8,!0),e.setUint32(t.value.lo,!0),e.setUint32(t.value.hi,!0);break;case s.Bool:e.setInt32(1,!0),e.setUint8(t.value?1:0);break;case s.Int32:e.setInt32(4,!0),e.setInt32(t.value,!0);break;case s.Int64:e.setInt32(8,!0),e.setUint32(t.value.lo,!0),e.setUint32(t.value.hi,!0);break;case s.String:var a=o.stringToBytes(t.value);e.setInt32(a.length,!0),e.writeBytes(a);break;case s.Bytes:var d=o.arrayToBuffer(t.value);e.setInt32(d.byteLength,!0),e.writeBytes(d);break;default:throw new i(n.ErrorCodes.Unsupported)}},e.exports=d},function(e,t,r){"use strict";var i=r(6),n=r(4),o={read:function(e){for(var t={},r=0,n=e.childNodes,a=n.length;r>>16&65535|0,a=0;0!==r;){r-=a=r>2e3?2e3:r;do{o=o+(n=n+t[i++]|0)|0}while(--a);n%=65521,o%=65521}return n|o<<16|0}},function(e,t,r){"use strict";var i=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var i=0;i<8;i++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();e.exports=function(e,t,r,n){var o=i,a=n+r;e^=-1;for(var s=n;s>>8^o[255&(e^t[s])];return-1^e}},function(e,t,r){"use strict";var i=r(5),n=!0,o=!0;try{String.fromCharCode.apply(null,[0])}catch(e){n=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(e){o=!1}for(var a=new i.Buf8(256),s=0;s<256;s++)a[s]=s>=252?6:s>=248?5:s>=240?4:s>=224?3:s>=192?2:1;function d(e,t){if(t<65537&&(e.subarray&&o||!e.subarray&&n))return String.fromCharCode.apply(null,i.shrinkBuf(e,t));for(var r="",a=0;a>>6,t[a++]=128|63&r):r<65536?(t[a++]=224|r>>>12,t[a++]=128|r>>>6&63,t[a++]=128|63&r):(t[a++]=240|r>>>18,t[a++]=128|r>>>12&63,t[a++]=128|r>>>6&63,t[a++]=128|63&r);return t},t.buf2binstring=function(e){return d(e,e.length)},t.binstring2buf=function(e){for(var t=new i.Buf8(e.length),r=0,n=t.length;r4)h[i++]=65533,r+=o-1;else{for(n&=2===o?31:3===o?15:7;o>1&&r1?h[i++]=65533:n<65536?h[i++]=n:(n-=65536,h[i++]=55296|n>>10&1023,h[i++]=56320|1023&n)}return d(h,i)},t.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;r>=0&&128==(192&e[r]);)r--;return r<0||0===r?t:r+a[e[r]]>t?r:t}},function(e,t,r){"use strict";e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},function(e,t,r){"use strict";e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},function(e,t,r){"use strict";var i=r(7),n=r(1),o=r(9),a=r(2),s=r(11),d=r(0),h=r(14),u=r(8),l=r(10),c=[{name:"EndOfHeader"},{name:"Comment"},{name:"CipherID"},{name:"CompressionFlags"},{name:"MasterSeed"},{name:"TransformSeed",ver:[3]},{name:"TransformRounds",ver:[3]},{name:"EncryptionIV"},{name:"ProtectedStreamKey",ver:[3]},{name:"StreamStartBytes",ver:[3]},{name:"InnerRandomStreamID",ver:[3]},{name:"KdfParameters",ver:[4]},{name:"PublicCustomData",ver:[4]}],f=[{name:"EndOfHeader"},{name:"InnerRandomStreamID"},{name:"InnerRandomStreamKey"},{name:"Binary",skipHeader:!0}],p={DefaultFileVersionMajor:4,DefaultFileVersionMinor:0,MaxFileVersionMajor:4,MaxFileVersionMinor:1,MaxSupportedVersion:4,FlagBinaryProtected:1,InnerHeaderBinaryFieldId:3,DefaultKdfAlgo:n.KdfId.Argon2,DefaultKdfSaltLength:32,DefaultKdfParallelism:1,DefaultKdfIterations:2,DefaultKdfMemory:1048576,DefaultKdfVersion:19},m={3:1,4:0},y=function(){this.versionMajor=void 0,this.versionMinor=void 0,this.dataCipherUuid=void 0,this.compression=void 0,this.masterSeed=void 0,this.transformSeed=void 0,this.keyEncryptionRounds=void 0,this.encryptionIV=void 0,this.protectedStreamKey=void 0,this.streamStartBytes=void 0,this.crsAlgorithm=void 0,this.endPos=void 0,this.kdfParameters=void 0,this.publicCustomData=void 0,Object.preventExtensions(this)};y.prototype._readSignature=function(e){if(e.byteLength<8)throw new a(n.ErrorCodes.FileCorrupt,"not enough data");var t=e.getUint32(!0),r=e.getUint32(!0);if(t!==n.Signatures.FileMagic||r!==n.Signatures.Sig2Kdbx)throw new a(n.ErrorCodes.BadSignature)},y.prototype._writeSignature=function(e){e.setUint32(n.Signatures.FileMagic,!0),e.setUint32(n.Signatures.Sig2Kdbx,!0)},y.prototype._readVersion=function(e){var t=e.getUint16(!0),r=e.getUint16(!0);if(r>p.MaxSupportedVersion)throw new a(n.ErrorCodes.InvalidVersion);this.versionMinor=t,this.versionMajor=r},y.prototype._writeVersion=function(e){e.setUint16(this.versionMinor,!0),e.setUint16(this.versionMajor,!0)},y.prototype._readCipherID=function(e){if(16!==e.byteLength)throw new a(n.ErrorCodes.Unsupported,"cipher");this.dataCipherUuid=new i(e)},y.prototype._writeCipherID=function(e){this._writeFieldSize(e,16),e.writeBytes(this.dataCipherUuid.bytes)},y.prototype._readCompressionFlags=function(e){var t=new DataView(e).getUint32(e,!0);if(t<0||t>=Object.keys(n.CompressionAlgorithm).length)throw new a(n.ErrorCodes.Unsupported,"compression");this.compression=t},y.prototype._writeCompressionFlags=function(e){this._writeFieldSize(e,4),e.setUint32(this.compression,!0)},y.prototype._readMasterSeed=function(e){this.masterSeed=e},y.prototype._writeMasterSeed=function(e){this._writeFieldBytes(e,this.masterSeed)},y.prototype._readTransformSeed=function(e){this.transformSeed=e},y.prototype._writeTransformSeed=function(e){this._writeFieldBytes(e,this.transformSeed)},y.prototype._readTransformRounds=function(e){this.keyEncryptionRounds=new s(e).getUint64(!0)},y.prototype._writeTransformRounds=function(e){this._writeFieldSize(e,8),e.setUint64(this.keyEncryptionRounds,!0)},y.prototype._readEncryptionIV=function(e){this.encryptionIV=e},y.prototype._writeEncryptionIV=function(e){this._writeFieldBytes(e,this.encryptionIV)},y.prototype._readProtectedStreamKey=function(e){this.protectedStreamKey=e},y.prototype._writeProtectedStreamKey=function(e){this._writeFieldBytes(e,this.protectedStreamKey)},y.prototype._readStreamStartBytes=function(e){this.streamStartBytes=e},y.prototype._writeStreamStartBytes=function(e){this._writeFieldBytes(e,this.streamStartBytes)},y.prototype._readInnerRandomStreamID=function(e){this.crsAlgorithm=new DataView(e).getUint32(e,!0)},y.prototype._writeInnerRandomStreamID=function(e){this._writeFieldSize(e,4),e.setUint32(this.crsAlgorithm,!0)},y.prototype._readInnerRandomStreamKey=function(e){this.protectedStreamKey=e},y.prototype._writeInnerRandomStreamKey=function(e){this._writeFieldBytes(e,this.protectedStreamKey)},y.prototype._readKdfParameters=function(e){this.kdfParameters=h.read(new s(e))},y.prototype._writeKdfParameters=function(e){var t=new s;this.kdfParameters.write(t),this._writeFieldBytes(e,t.getWrittenBytes())},y.prototype._readPublicCustomData=function(e){this.publicCustomData=h.read(new s(e))},y.prototype._hasPublicCustomData=function(){return this.publicCustomData},y.prototype._writePublicCustomData=function(e){if(this.publicCustomData){var t=new s;this.publicCustomData.write(t),this._writeFieldBytes(e,t.getWrittenBytes())}},y.prototype._readBinary=function(e,t){var r=new DataView(e).getUint8(0)&p.FlagBinaryProtected,i=e.slice(1),n=r?o.fromBinary(i):i,a=Object.keys(t.kdbx.binaries).length;t.kdbx.binaries[a]=n},y.prototype._writeBinary=function(e,t){if(!(this.versionMajor<4))for(var r=t.kdbx.binaries.hashOrder,i=0;i0&&(i=e.readBytes(o));var a=t[n];if(a){var s=this["_read"+a.name];s&&s.call(this,i,r)}return 0!==n},y.prototype._writeField=function(e,t,r,i){var n=r[t];if(n){if(n.ver&&n.ver.indexOf(this.versionMajor)<0)return;var o=this["_write"+n.name];if(o){var a=this["_has"+n.name];if(a&&!a.call(this))return;n.skipHeader||e.setUint8(t),o.call(this,e,i)}}},y.prototype._readFieldSize=function(e){return this.versionMajor>=4?e.getUint32(!0):e.getUint16(!0)},y.prototype._writeFieldSize=function(e,t){this.versionMajor>=4?e.setUint32(t,!0):e.setUint16(t,!0)},y.prototype._writeFieldBytes=function(e,t){this._writeFieldSize(e,t.byteLength),e.writeBytes(t)},y.prototype._validate=function(){if(void 0===this.dataCipherUuid)throw new a(n.ErrorCodes.FileCorrupt,"no cipher in header");if(void 0===this.compression)throw new a(n.ErrorCodes.FileCorrupt,"no compression in header");if(!this.masterSeed)throw new a(n.ErrorCodes.FileCorrupt,"no master seed in header");if(this.versionMajor<4&&!this.transformSeed)throw new a(n.ErrorCodes.FileCorrupt,"no transform seed in header");if(this.versionMajor<4&&!this.keyEncryptionRounds)throw new a(n.ErrorCodes.FileCorrupt,"no key encryption rounds in header");if(!this.encryptionIV)throw new a(n.ErrorCodes.FileCorrupt,"no encryption iv in header");if(this.versionMajor<4&&!this.protectedStreamKey)throw new a(n.ErrorCodes.FileCorrupt,"no protected stream key in header");if(this.versionMajor<4&&!this.streamStartBytes)throw new a(n.ErrorCodes.FileCorrupt,"no stream start bytes in header");if(this.versionMajor<4&&!this.crsAlgorithm)throw new a(n.ErrorCodes.FileCorrupt,"no crs algorithm in header");if(this.versionMajor>=4&&!this.kdfParameters)throw new a(n.ErrorCodes.FileCorrupt,"no kdf parameters in header")},y.prototype._validateInner=function(){if(!this.protectedStreamKey)throw new a(n.ErrorCodes.FileCorrupt,"no protected stream key in header");if(!this.crsAlgorithm)throw new a(n.ErrorCodes.FileCorrupt,"no crs algorithm in header")},y.prototype._createKdfParameters=function(e){switch(e||(e=p.DefaultKdfAlgo),e){case n.KdfId.Argon2:this.kdfParameters=new h,this.kdfParameters.set("$UUID",h.ValueType.Bytes,d.base64ToBytes(n.KdfId.Argon2)),this.kdfParameters.set("S",h.ValueType.Bytes,l.getBytes(p.DefaultKdfSaltLength)),this.kdfParameters.set("P",h.ValueType.UInt32,p.DefaultKdfParallelism),this.kdfParameters.set("I",h.ValueType.UInt64,new u(p.DefaultKdfIterations)),this.kdfParameters.set("M",h.ValueType.UInt64,new u(p.DefaultKdfMemory)),this.kdfParameters.set("V",h.ValueType.UInt32,p.DefaultKdfVersion);break;case n.KdfId.Aes:this.kdfParameters=new h,this.kdfParameters.set("$UUID",h.ValueType.Bytes,d.base64ToBytes(n.KdfId.Aes)),this.kdfParameters.set("S",h.ValueType.Bytes,l.getBytes(p.DefaultKdfSaltLength)),this.kdfParameters.set("R",h.ValueType.UInt64,new u(n.Defaults.KeyEncryptionRounds));break;default:throw new a(n.ErrorCodes.InvalidArg,"bad KDF algo")}},y.prototype.write=function(e){this._validate(),this._writeSignature(e),this._writeVersion(e);for(var t=1;t>4&15]),r.push(t[15&i[n]]);return r.join("")},i.prototype._reset=function(){this.counterWords[0]=0,this.counterWords[1]=0,this.blockUsed=64},i.prototype._incrementCounter=function(){this.counterWords[0]=this.counterWords[0]+1&4294967295,0===this.counterWords[0]&&(this.counterWords[1]=this.counterWords[1]+1&4294967295)},i.prototype._generateBlock=function(){for(var e,t=this.sigmaWords[0],r=this.keyWords[0],i=this.keyWords[1],n=this.keyWords[2],o=this.keyWords[3],a=this.sigmaWords[1],s=this.nonceWords[0],d=this.nonceWords[1],h=this.counterWords[0],u=this.counterWords[1],l=this.sigmaWords[2],c=this.keyWords[4],f=this.keyWords[5],p=this.keyWords[6],m=this.keyWords[7],y=this.sigmaWords[3],g=t,_=r,b=i,v=n,w=o,k=a,C=s,x=d,E=h,B=u,T=l,A=c,S=f,U=p,D=m,I=y,N=0;N>>25)+g)<<9|e>>>23)+w)<<13|e>>>19)+E)<<18|e>>>14,k^=(e=(_^=(e=(U^=(e=(B^=(e=k+_)<<7|e>>>25)+k)<<9|e>>>23)+B)<<13|e>>>19)+U)<<18|e>>>14,T^=(e=(C^=(e=(b^=(e=(D^=(e=T+C)<<7|e>>>25)+T)<<9|e>>>23)+D)<<13|e>>>19)+b)<<18|e>>>14,I^=(e=(A^=(e=(x^=(e=(v^=(e=I+A)<<7|e>>>25)+I)<<9|e>>>23)+v)<<13|e>>>19)+x)<<18|e>>>14,g^=(e=(v^=(e=(b^=(e=(_^=(e=g+v)<<7|e>>>25)+g)<<9|e>>>23)+_)<<13|e>>>19)+b)<<18|e>>>14,k^=(e=(w^=(e=(x^=(e=(C^=(e=k+w)<<7|e>>>25)+k)<<9|e>>>23)+C)<<13|e>>>19)+x)<<18|e>>>14,T^=(e=(B^=(e=(E^=(e=(A^=(e=T+B)<<7|e>>>25)+T)<<9|e>>>23)+A)<<13|e>>>19)+E)<<18|e>>>14,I^=(e=(D^=(e=(U^=(e=(S^=(e=I+D)<<7|e>>>25)+I)<<9|e>>>23)+S)<<13|e>>>19)+U)<<18|e>>>14;g+=t,_+=r,b+=i,v+=n,w+=o,k+=a,C+=s,x+=d,E+=h,B+=u,T+=l,A+=c,S+=f,U+=p,D+=m,I+=y,this.block[0]=g>>>0&255,this.block[1]=g>>>8&255,this.block[2]=g>>>16&255,this.block[3]=g>>>24&255,this.block[4]=_>>>0&255,this.block[5]=_>>>8&255,this.block[6]=_>>>16&255,this.block[7]=_>>>24&255,this.block[8]=b>>>0&255,this.block[9]=b>>>8&255,this.block[10]=b>>>16&255,this.block[11]=b>>>24&255,this.block[12]=v>>>0&255,this.block[13]=v>>>8&255,this.block[14]=v>>>16&255,this.block[15]=v>>>24&255,this.block[16]=w>>>0&255,this.block[17]=w>>>8&255,this.block[18]=w>>>16&255,this.block[19]=w>>>24&255,this.block[20]=k>>>0&255,this.block[21]=k>>>8&255,this.block[22]=k>>>16&255,this.block[23]=k>>>24&255,this.block[24]=C>>>0&255,this.block[25]=C>>>8&255,this.block[26]=C>>>16&255,this.block[27]=C>>>24&255,this.block[28]=x>>>0&255,this.block[29]=x>>>8&255,this.block[30]=x>>>16&255,this.block[31]=x>>>24&255,this.block[32]=E>>>0&255,this.block[33]=E>>>8&255,this.block[34]=E>>>16&255,this.block[35]=E>>>24&255,this.block[36]=B>>>0&255,this.block[37]=B>>>8&255,this.block[38]=B>>>16&255,this.block[39]=B>>>24&255,this.block[40]=T>>>0&255,this.block[41]=T>>>8&255,this.block[42]=T>>>16&255,this.block[43]=T>>>24&255,this.block[44]=A>>>0&255,this.block[45]=A>>>8&255,this.block[46]=A>>>16&255,this.block[47]=A>>>24&255,this.block[48]=S>>>0&255,this.block[49]=S>>>8&255,this.block[50]=S>>>16&255,this.block[51]=S>>>24&255,this.block[52]=U>>>0&255,this.block[53]=U>>>8&255,this.block[54]=U>>>16&255,this.block[55]=U>>>24&255,this.block[56]=D>>>0&255,this.block[57]=D>>>8&255,this.block[58]=D>>>16&255,this.block[59]=D>>>24&255,this.block[60]=I>>>0&255,this.block[61]=I>>>8&255,this.block[62]=I>>>16&255,this.block[63]=I>>>24&255},e.exports=i},function(e,t,r){"use strict";function i(e,t){this.sigmaWords=[1634760805,857760878,2036477234,1797285236],this.block=new Uint8Array(64),this.blockUsed=64,this.x=new Uint32Array(16);var r=new Uint32Array(16);r[0]=this.sigmaWords[0],r[1]=this.sigmaWords[1],r[2]=this.sigmaWords[2],r[3]=this.sigmaWords[3],r[4]=o(e,0),r[5]=o(e,4),r[6]=o(e,8),r[7]=o(e,12),r[8]=o(e,16),r[9]=o(e,20),r[10]=o(e,24),r[11]=o(e,28),r[12]=0,12===t.length?(r[13]=o(t,0),r[14]=o(t,4),r[15]=o(t,8)):(r[13]=0,r[14]=o(t,0),r[15]=o(t,4)),this.input=r}function n(e,t,r,i,n){e[t]+=e[r],e[n]=s(e[n]^e[t],16),e[i]+=e[n],e[r]=s(e[r]^e[i],12),e[t]+=e[r],e[n]=s(e[n]^e[t],8),e[i]+=e[n],e[r]=s(e[r]^e[i],7)}function o(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function a(e,t,r){e[t]=r,r>>>=8,e[t+1]=r,r>>>=8,e[t+2]=r,r>>>=8,e[t+3]=r}function s(e,t){return e<>>32-t}i.prototype.getBytes=function(e){for(var t=new Uint8Array(e),r=0;r0;e-=2)n(r,0,4,8,12),n(r,1,5,9,13),n(r,2,6,10,14),n(r,3,7,11,15),n(r,0,5,10,15),n(r,1,6,11,12),n(r,2,7,8,13),n(r,3,4,9,14);for(e=16;e--;)r[e]+=t[e];for(e=16;e--;)a(i,4*e,r[e]);t[12]+=1,t[12]||(t[13]+=1)},i.prototype.encrypt=function(e){for(var t=e.length,r=new Uint8Array(t),i=0,n=this.block;i0;){var d=Math.min(r,1e4);r-=d;var h=o*d;n=s(e,n,a.length===h?a.buffer:i.arrayToBuffer(a.subarray(0,h)))}return n.then((function(e){return new Uint8Array(e)}))}function s(e,t,r){return t.then((function(t){return e.encrypt(r,t)})).then((function(e){var t=i.arrayToBuffer(new Uint8Array(e).subarray(-32,-16));return i.zeroBuffer(e),t}))}e.exports.encrypt=function(e,t,r){var s=n.createAesCbc();return s.importKey(i.arrayToBuffer(t)).then((function(){for(var t=[],i=0;i<32;i+=o)t.push(a(s,e.subarray(i,i+o),r));return Promise.all(t)})).then((function(e){var t=new Uint8Array(32);return e.forEach((function(e,r){for(var n=r*o,a=0;a\n \n";return a.stringToBytes(t)},e.exports=u},function(e,t,r){"use strict";var i=r(6),n=r(4),o=function(){this.creationTime=void 0,this.lastModTime=void 0,this.lastAccessTime=void 0,this.expiryTime=void 0,this.expires=void 0,this.usageCount=void 0,this.locationChanged=new Date,Object.preventExtensions(this)};o.prototype._readNode=function(e){switch(e.tagName){case i.Elem.CreationTime:this.creationTime=n.getDate(e);break;case i.Elem.LastModTime:this.lastModTime=n.getDate(e);break;case i.Elem.LastAccessTime:this.lastAccessTime=n.getDate(e);break;case i.Elem.ExpiryTime:this.expiryTime=n.getDate(e);break;case i.Elem.Expires:this.expires=n.getBoolean(e);break;case i.Elem.UsageCount:this.usageCount=n.getNumber(e);break;case i.Elem.LocationChanged:this.locationChanged=n.getDate(e)}},o.prototype.clone=function(){var e=new o;return e.creationTime=this.creationTime,e.lastModTime=this.lastModTime,e.lastAccessTime=this.lastAccessTime,e.expiryTime=this.expiryTime,e.expires=this.expires,e.usageCount=this.usageCount,e.locationChanged=this.locationChanged,e},o.prototype.update=function(){var e=new Date;this.lastModTime=e,this.lastAccessTime=e},o.prototype.write=function(e,t){var r=n.addChildNode(e,i.Elem.Times);t.setXmlDate(n.addChildNode(r,i.Elem.CreationTime),this.creationTime),t.setXmlDate(n.addChildNode(r,i.Elem.LastModTime),this.lastModTime),t.setXmlDate(n.addChildNode(r,i.Elem.LastAccessTime),this.lastAccessTime),t.setXmlDate(n.addChildNode(r,i.Elem.ExpiryTime),this.expiryTime),n.setBoolean(n.addChildNode(r,i.Elem.Expires),this.expires),n.setNumber(n.addChildNode(r,i.Elem.UsageCount),this.usageCount),t.setXmlDate(n.addChildNode(r,i.Elem.LocationChanged),this.locationChanged)},o.create=function(){var e=new o,t=new Date;return e.creationTime=t,e.lastModTime=t,e.lastAccessTime=t,e.expiryTime=t,e.expires=!1,e.usageCount=0,e.locationChanged=t,e},o.read=function(e){for(var t=new o,r=0,i=e.childNodes,n=i.length;rt.times.lastModTime){if(!this.history.some((function(e){return+e.times.lastModTime==+t.times.lastModTime}))){var i=new l;i.copyFrom(t),r.push(i)}}this.history=this._mergeHistory(r,t.times.lastModTime)}},l.prototype._mergeHistory=function(e,t){this.history.sort((function(e,t){return e.times.lastModTime-t.times.lastModTime})),e.sort((function(e,t){return e.times.lastModTime-t.times.lastModTime}));var r={},i={};this.history.forEach((function(e){r[e.times.lastModTime.getTime()]=e})),e.forEach((function(e){i[e.times.lastModTime.getTime()]=e}));for(var n=0,o=0,a=[];nu){if(!this._editState||this._editState.deleted.indexOf(u)<0){var c=new l;c.copyFrom(d),a.push(c)}o++}else(this._editState&&this._editState.added.indexOf(h)>=0||h>t)&&a.push(s),n++;else a.push(s),n++,o++}return a},l.create=function(e,t){var r=new l(t);return r.uuid=d.random(),r.icon=a.Icons.Key,r.times=h.create(),r.parentGroup=t,r._setField("Title","",e.memoryProtection.title),r._setField("UserName",e.defaultUser||"",e.memoryProtection.userName),r._setField("Password","",e.memoryProtection.password),r._setField("URL","",e.memoryProtection.url),r._setField("Notes","",e.memoryProtection.notes),r.autoType.enabled="boolean"!=typeof t.enableAutoType||t.enableAutoType,r.autoType.obfuscation=a.AutoTypeObfuscationOptions.None,r},l.read=function(e,t,r){for(var i=new l,n=0,o=e.childNodes,a=o.length;n=0?t[i].splice(r,0,e):t[i].push(e);else{var a=new Date;e instanceof h?e.forEach((function(e,t){this.addDeletedObject((e||t).uuid,a)}),this):this.addDeletedObject(e.uuid,a)}e.parentGroup=t,e.times.locationChanged=new Date}},y.prototype.addDeletedObject=function(e,t){var r=new l;r.uuid=e,r.deletionTime=t,this.deletedObjects.push(r)},y.prototype.remove=function(e){var t=null;this.meta.recycleBinEnabled&&(this.createRecycleBin(),t=this.getGroup(this.meta.recycleBinUuid)),this.move(e,t)},y.prototype.createBinary=function(e){return this.binaries.add(e)},y.prototype.importEntry=function(e,t,r){var i=new u,n=c.random();i.copyFrom(e),i.uuid=n,e.history.forEach((function(e){var t=new u;t.copyFrom(e),t.uuid=n,i.history.push(t)}));var o={},a={};return i.history.concat(i).forEach((function(e){e.customIcon&&(a[e.customIcon]=e.customIcon),Object.values(e.binaries).forEach((function(e){e.ref&&(o[e.ref]=e)}))})),Object.values(o).forEach((function(e){var t=r.binaries[e.ref];t&&!this.binaries[e.ref]&&(this.binaries[e.ref]=t)}),this),Object.values(a).forEach((function(e){var t=r.meta.customIcons[e];t&&(this.meta.customIcons[e]=t)}),this),t.entries.push(i),i.parentGroup=t,i.times.update(),i},y.prototype.cleanup=function(e){var t=new Date,r=e&&e.historyRules&&"number"==typeof this.meta.historyMaxItems&&this.meta.historyMaxItems>=0?this.meta.historyMaxItems:1/0,i={},n={},o=function(e){e&&e.customIcon&&(i[e.customIcon]=!0),e&&e.binaries&&Object.keys(e.binaries).forEach((function(t){e.binaries[t]&&e.binaries[t].ref&&(n[e.binaries[t].ref]=!0)}))};this.getDefaultGroup().forEach((function(e,t){e&&e.history.length>r&&e.removeHistory(0,e.history.length-r),e&&o(e),e&&e.history&&e.history.forEach((function(e){o(e)})),t&&t.customIcon&&(i[t.customIcon]=!0)})),e&&e.customIcons&&Object.keys(this.meta.customIcons).forEach((function(e){if(!i[e]){var r=new c(e);this.addDeletedObject(r,t),delete this.meta.customIcons[e]}}),this),e&&e.binaries&&Object.keys(this.binaries).forEach((function(e){n[e]||delete this.binaries[e]}),this)},y.prototype.merge=function(e){var t=this.getDefaultGroup(),r=e.getDefaultGroup();if(!t||!r)throw new n(f.ErrorCodes.MergeError,"no default group");if(!t.uuid.equals(r.uuid))throw new n(f.ErrorCodes.MergeError,"default group is different");var i=this._getObjectMap();e.deletedObjects.forEach((function(e){i.deleted[e.uuid]||(this.deletedObjects.push(e),i.deleted[e.uuid]=e.deletionTime)}),this),Object.keys(e.binaries).forEach((function(t){this.binaries[t]||i.deleted[t]||(this.binaries[t]=e.binaries[t])}),this),i.remote=e._getObjectMap().objects,this.meta.merge(e.meta,i),t.merge(i),this.cleanup({historyRules:!0,customIcons:!0,binaries:!0})},y.prototype.getLocalEditState=function(){var e={};return this.getDefaultGroup().forEach((function(t){t&&t._editState&&(e[t.uuid]=t._editState)})),this.meta._editState&&(e.meta=this.meta._editState),e},y.prototype.setLocalEditState=function(e){this.getDefaultGroup().forEach((function(t){t&&e[t.uuid]&&(t._editState=e[t.uuid])})),e.meta&&(this.meta._editState=e.meta)},y.prototype.removeLocalEditState=function(){this.getDefaultGroup().forEach((function(e){e&&(e._editState=void 0)})),this.meta._editState=void 0},y.prototype.upgrade=function(){this.setVersion(a.MaxFileVersion)},y.prototype.setVersion=function(e){this.meta.headerHash=null,this.meta.settingsChanged=new Date,this.header.setVersion(e)},y.prototype.setKdf=function(e){this.meta.headerHash=null,this.meta.settingsChanged=new Date,this.header.setKdf(e)},y.prototype._getObjectMap=function(){var e={},t={};return this.getDefaultGroup().forEach((function(t,r){var i=t||r;if(e[i.uuid])throw new n(f.ErrorCodes.MergeError,"Duplicate: "+i.uuid);e[i.uuid]=i})),this.deletedObjects.forEach((function(e){t[e.uuid]=e.deletionTime})),{objects:e,deleted:t}},y.prototype._loadFromXml=function(e){if(this.xml.documentElement.tagName!==p.Elem.DocNode)throw new n(f.ErrorCodes.FileCorrupt,"bad xml root");this._parseMeta(e);var t=this;return this.binaries.hash().then((function(){return t._parseRoot(e),t}))},y.prototype._parseMeta=function(e){var t=m.getChildNode(this.xml.documentElement,p.Elem.Meta,"no meta node");this.meta=s.read(t,e)},y.prototype._parseRoot=function(e){this.groups=[],this.deletedObjects=[];for(var t=0,r=m.getChildNode(this.xml.documentElement,p.Elem.Root,"no root node").childNodes,i=r.length;t0?t.windowBits=-t.windowBits:t.gzip&&t.windowBits>0&&t.windowBits<16&&(t.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=0;var r=i.deflateInit2(this.strm,t.level,t.method,t.windowBits,t.memLevel,t.strategy);if(0!==r)throw new Error(a[r]);if(t.header&&i.deflateSetHeader(this.strm,t.header),t.dictionary){var u;if(u="string"==typeof t.dictionary?o.string2buf(t.dictionary):"[object ArrayBuffer]"===d.call(t.dictionary)?new Uint8Array(t.dictionary):t.dictionary,0!==(r=i.deflateSetDictionary(this.strm,u)))throw new Error(a[r]);this._dict_set=!0}}function u(e,t){var r=new h(t);if(r.push(e,!0),r.err)throw r.msg;return r.result}h.prototype.push=function(e,t){var r,a,s=this.strm,h=this.options.chunkSize;if(this.ended)return!1;a=t===~~t?t:!0===t?4:0,"string"==typeof e?s.input=o.string2buf(e):"[object ArrayBuffer]"===d.call(e)?s.input=new Uint8Array(e):s.input=e,s.next_in=0,s.avail_in=s.input.length;do{if(0===s.avail_out&&(s.output=new n.Buf8(h),s.next_out=0,s.avail_out=h),1!==(r=i.deflate(s,a))&&0!==r)return this.onEnd(r),this.ended=!0,!1;0!==s.avail_out&&(0!==s.avail_in||4!==a&&2!==a)||("string"===this.options.to?this.onData(o.buf2binstring(n.shrinkBuf(s.output,s.next_out))):this.onData(n.shrinkBuf(s.output,s.next_out)))}while((s.avail_in>0||0===s.avail_out)&&1!==r);return 4===a?(r=i.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,0===r):2!==a||(this.onEnd(0),s.avail_out=0,!0)},h.prototype.onData=function(e){this.chunks.push(e)},h.prototype.onEnd=function(e){0===e&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=n.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},t.Deflate=h,t.deflate=u,t.deflateRaw=function(e,t){return(t=t||{}).raw=!0,u(e,t)},t.gzip=function(e,t){return(t=t||{}).gzip=!0,u(e,t)}},function(e,t,r){"use strict";var i,n=r(5),o=r(34),a=r(17),s=r(18),d=r(12),h=-2,u=258,l=262,c=103,f=113,p=666;function m(e,t){return e.msg=d[t],t}function y(e){return(e<<1)-(e>4?9:0)}function g(e){for(var t=e.length;--t>=0;)e[t]=0}function _(e){var t=e.state,r=t.pending;r>e.avail_out&&(r=e.avail_out),0!==r&&(n.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function b(e,t){o._tr_flush_block(e,e.block_start>=0?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,_(e.strm)}function v(e,t){e.pending_buf[e.pending++]=t}function w(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function k(e,t){var r,i,n=e.max_chain_length,o=e.strstart,a=e.prev_length,s=e.nice_match,d=e.strstart>e.w_size-l?e.strstart-(e.w_size-l):0,h=e.window,c=e.w_mask,f=e.prev,p=e.strstart+u,m=h[o+a-1],y=h[o+a];e.prev_length>=e.good_match&&(n>>=2),s>e.lookahead&&(s=e.lookahead);do{if(h[(r=t)+a]===y&&h[r+a-1]===m&&h[r]===h[o]&&h[++r]===h[o+1]){o+=2,r++;do{}while(h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&h[++o]===h[++r]&&oa){if(e.match_start=t,a=i,i>=s)break;m=h[o+a-1],y=h[o+a]}}}while((t=f[t&c])>d&&0!=--n);return a<=e.lookahead?a:e.lookahead}function C(e){var t,r,i,o,d,h,u,c,f,p,m=e.w_size;do{if(o=e.window_size-e.lookahead-e.strstart,e.strstart>=m+(m-l)){n.arraySet(e.window,e.window,m,m,0),e.match_start-=m,e.strstart-=m,e.block_start-=m,t=r=e.hash_size;do{i=e.head[--t],e.head[t]=i>=m?i-m:0}while(--r);t=r=m;do{i=e.prev[--t],e.prev[t]=i>=m?i-m:0}while(--r);o+=m}if(0===e.strm.avail_in)break;if(h=e.strm,u=e.window,c=e.strstart+e.lookahead,f=o,p=void 0,(p=h.avail_in)>f&&(p=f),r=0===p?0:(h.avail_in-=p,n.arraySet(u,h.input,h.next_in,p,c),1===h.state.wrap?h.adler=a(h.adler,u,p,c):2===h.state.wrap&&(h.adler=s(h.adler,u,p,c)),h.next_in+=p,h.total_in+=p,p),e.lookahead+=r,e.lookahead+e.insert>=3)for(d=e.strstart-e.insert,e.ins_h=e.window[d],e.ins_h=(e.ins_h<=3&&(e.ins_h=(e.ins_h<=3)if(i=o._tr_tally(e,e.strstart-e.match_start,e.match_length-3),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=3){e.match_length--;do{e.strstart++,e.ins_h=(e.ins_h<=3&&(e.ins_h=(e.ins_h<4096)&&(e.match_length=2)),e.prev_length>=3&&e.match_length<=e.prev_length){n=e.strstart+e.lookahead-3,i=o._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-3),e.lookahead-=e.prev_length-1,e.prev_length-=2;do{++e.strstart<=n&&(e.ins_h=(e.ins_h<15&&(s=2,i-=16),o<1||o>9||8!==r||i<8||i>15||t<0||t>9||a<0||a>4)return m(e,h);8===i&&(i=9);var d=new T;return e.state=d,d.strm=e,d.wrap=s,d.gzhead=null,d.w_bits=i,d.w_size=1<e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(C(e),0===e.lookahead&&0===t)return 1;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var i=e.block_start+r;if((0===e.strstart||e.strstart>=i)&&(e.lookahead=e.strstart-i,e.strstart=i,b(e,!1),0===e.strm.avail_out))return 1;if(e.strstart-e.block_start>=e.w_size-l&&(b(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(b(e,!0),0===e.strm.avail_out?3:4):(e.strstart>e.block_start&&(b(e,!1),e.strm.avail_out),1)})),new B(4,4,8,4,x),new B(4,5,16,8,x),new B(4,6,32,32,x),new B(4,4,16,16,E),new B(8,16,32,32,E),new B(8,16,128,128,E),new B(8,32,128,256,E),new B(32,128,258,1024,E),new B(32,258,258,4096,E)],t.deflateInit=function(e,t){return U(e,t,8,15,8,0)},t.deflateInit2=U,t.deflateReset=S,t.deflateResetKeep=A,t.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?h:(e.state.gzhead=t,0):h},t.deflate=function(e,t){var r,n,a,d;if(!e||!e.state||t>5||t<0)return e?m(e,h):h;if(n=e.state,!e.output||!e.input&&0!==e.avail_in||n.status===p&&4!==t)return m(e,0===e.avail_out?-5:h);if(n.strm=e,r=n.last_flush,n.last_flush=t,42===n.status)if(2===n.wrap)e.adler=0,v(n,31),v(n,139),v(n,8),n.gzhead?(v(n,(n.gzhead.text?1:0)+(n.gzhead.hcrc?2:0)+(n.gzhead.extra?4:0)+(n.gzhead.name?8:0)+(n.gzhead.comment?16:0)),v(n,255&n.gzhead.time),v(n,n.gzhead.time>>8&255),v(n,n.gzhead.time>>16&255),v(n,n.gzhead.time>>24&255),v(n,9===n.level?2:n.strategy>=2||n.level<2?4:0),v(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(v(n,255&n.gzhead.extra.length),v(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=s(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(v(n,0),v(n,0),v(n,0),v(n,0),v(n,0),v(n,9===n.level?2:n.strategy>=2||n.level<2?4:0),v(n,3),n.status=f);else{var l=8+(n.w_bits-8<<4)<<8;l|=(n.strategy>=2||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(l|=32),l+=31-l%31,n.status=f,w(n,l),0!==n.strstart&&(w(n,e.adler>>>16),w(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(a=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>a&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),_(e),a=n.pending,n.pending!==n.pending_buf_size));)v(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>a&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){a=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>a&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),_(e),a=n.pending,n.pending===n.pending_buf_size)){d=1;break}d=n.gzindexa&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),0===d&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){a=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>a&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),_(e),a=n.pending,n.pending===n.pending_buf_size)){d=1;break}d=n.gzindexa&&(e.adler=s(e.adler,n.pending_buf,n.pending-a,a)),0===d&&(n.status=c)}else n.status=c;if(n.status===c&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&_(e),n.pending+2<=n.pending_buf_size&&(v(n,255&e.adler),v(n,e.adler>>8&255),e.adler=0,n.status=f)):n.status=f),0!==n.pending){if(_(e),0===e.avail_out)return n.last_flush=-1,0}else if(0===e.avail_in&&y(t)<=y(r)&&4!==t)return m(e,-5);if(n.status===p&&0!==e.avail_in)return m(e,-5);if(0!==e.avail_in||0!==n.lookahead||0!==t&&n.status!==p){var k=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(C(e),0===e.lookahead)){if(0===t)return 1;break}if(e.match_length=0,r=o._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(b(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(b(e,!0),0===e.strm.avail_out?3:4):e.last_lit&&(b(e,!1),0===e.strm.avail_out)?1:2}(n,t):3===n.strategy?function(e,t){for(var r,i,n,a,s=e.window;;){if(e.lookahead<=u){if(C(e),e.lookahead<=u&&0===t)return 1;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=3&&e.strstart>0&&(i=s[n=e.strstart-1])===s[++n]&&i===s[++n]&&i===s[++n]){a=e.strstart+u;do{}while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&ne.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=3?(r=o._tr_tally(e,1,e.match_length-3),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=o._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(b(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(b(e,!0),0===e.strm.avail_out?3:4):e.last_lit&&(b(e,!1),0===e.strm.avail_out)?1:2}(n,t):i[n.level].func(n,t);if(3!==k&&4!==k||(n.status=p),1===k||3===k)return 0===e.avail_out&&(n.last_flush=-1),0;if(2===k&&(1===t?o._tr_align(n):5!==t&&(o._tr_stored_block(n,0,0,!1),3===t&&(g(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),_(e),0===e.avail_out))return n.last_flush=-1,0}return 4!==t?0:n.wrap<=0?1:(2===n.wrap?(v(n,255&e.adler),v(n,e.adler>>8&255),v(n,e.adler>>16&255),v(n,e.adler>>24&255),v(n,255&e.total_in),v(n,e.total_in>>8&255),v(n,e.total_in>>16&255),v(n,e.total_in>>24&255)):(w(n,e.adler>>>16),w(n,65535&e.adler)),_(e),n.wrap>0&&(n.wrap=-n.wrap),0!==n.pending?0:1)},t.deflateEnd=function(e){var t;return e&&e.state?42!==(t=e.state.status)&&69!==t&&73!==t&&91!==t&&t!==c&&t!==f&&t!==p?m(e,h):(e.state=null,t===f?m(e,-3):0):h},t.deflateSetDictionary=function(e,t){var r,i,o,s,d,u,l,c,f=t.length;if(!e||!e.state)return h;if(2===(s=(r=e.state).wrap)||1===s&&42!==r.status||r.lookahead)return h;for(1===s&&(e.adler=a(e.adler,t,f,0)),r.wrap=0,f>=r.w_size&&(0===s&&(g(r.head),r.strstart=0,r.block_start=0,r.insert=0),c=new n.Buf8(r.w_size),n.arraySet(c,t,f-r.w_size,r.w_size,0),t=c,f=r.w_size),d=e.avail_in,u=e.next_in,l=e.input,e.avail_in=f,e.next_in=0,e.input=t,C(r);r.lookahead>=3;){i=r.strstart,o=r.lookahead-2;do{r.ins_h=(r.ins_h<=0;)e[t]=0}var o=256,a=286,s=30,d=15,h=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],u=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],l=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],c=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],f=new Array(576);n(f);var p=new Array(60);n(p);var m=new Array(512);n(m);var y=new Array(256);n(y);var g=new Array(29);n(g);var _,b,v,w=new Array(s);function k(e,t,r,i,n){this.static_tree=e,this.extra_bits=t,this.extra_base=r,this.elems=i,this.max_length=n,this.has_stree=e&&e.length}function C(e,t){this.dyn_tree=e,this.max_code=0,this.stat_desc=t}function x(e){return e<256?m[e]:m[256+(e>>>7)]}function E(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function B(e,t,r){e.bi_valid>16-r?(e.bi_buf|=t<>16-e.bi_valid,e.bi_valid+=r-16):(e.bi_buf|=t<>>=1,r<<=1}while(--t>0);return r>>>1}function S(e,t,r){var i,n,o=new Array(16),a=0;for(i=1;i<=d;i++)o[i]=a=a+r[i-1]<<1;for(n=0;n<=t;n++){var s=e[2*n+1];0!==s&&(e[2*n]=A(o[s]++,s))}}function U(e){var t;for(t=0;t8?E(e,e.bi_buf):e.bi_valid>0&&(e.pending_buf[e.pending++]=e.bi_buf),e.bi_buf=0,e.bi_valid=0}function I(e,t,r,i){var n=2*t,o=2*r;return e[n]>1;r>=1;r--)N(e,o,r);n=h;do{r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],N(e,o,1),i=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=i,o[2*n]=o[2*r]+o[2*i],e.depth[n]=(e.depth[r]>=e.depth[i]?e.depth[r]:e.depth[i])+1,o[2*r+1]=o[2*i+1]=n,e.heap[1]=n++,N(e,o,1)}while(e.heap_len>=2);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,i,n,o,a,s,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,c=t.stat_desc.has_stree,f=t.stat_desc.extra_bits,p=t.stat_desc.extra_base,m=t.stat_desc.max_length,y=0;for(o=0;o<=d;o++)e.bl_count[o]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<573;r++)(o=h[2*h[2*(i=e.heap[r])+1]+1]+1)>m&&(o=m,y++),h[2*i+1]=o,i>u||(e.bl_count[o]++,a=0,i>=p&&(a=f[i-p]),s=h[2*i],e.opt_len+=s*(o+a),c&&(e.static_len+=s*(l[2*i+1]+a)));if(0!==y){do{for(o=m-1;0===e.bl_count[o];)o--;e.bl_count[o]--,e.bl_count[o+1]+=2,e.bl_count[m]--,y-=2}while(y>0);for(o=m;0!==o;o--)for(i=e.bl_count[o];0!==i;)(n=e.heap[--r])>u||(h[2*n+1]!==o&&(e.opt_len+=(o-h[2*n+1])*h[2*n],h[2*n+1]=o),i--)}}(e,t),S(o,u,e.bl_count)}function F(e,t,r){var i,n,o=-1,a=t[1],s=0,d=7,h=4;for(0===a&&(d=138,h=3),t[2*(r+1)+1]=65535,i=0;i<=r;i++)n=a,a=t[2*(i+1)+1],++s>=7;i0?(2===e.strm.data_type&&(e.strm.data_type=function(e){var t,r=4093624447;for(t=0;t<=31;t++,r>>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return 0;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return 1;for(t=32;t=3&&0===e.bl_tree[2*c[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t}(e),n=e.opt_len+3+7>>>3,(a=e.static_len+3+7>>>3)<=n&&(n=a)):n=a=r+5,r+4<=n&&-1!==t?V(e,t,r,i):4===e.strategy||a===n?(B(e,2+(i?1:0),3),P(e,f,p)):(B(e,4+(i?1:0),3),function(e,t,r,i){var n;for(B(e,t-257,5),B(e,r-1,5),B(e,i-4,4),n=0;n>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(y[r]+o+1)]++,e.dyn_dtree[2*x(t)]++),e.last_lit===e.lit_bufsize-1},t._tr_align=function(e){B(e,2,3),T(e,256,f),function(e){16===e.bi_valid?(E(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):e.bi_valid>=8&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},function(e,t,r){"use strict";var i=r(36),n=r(5),o=r(19),a=r(21),s=r(12),d=r(20),h=r(39),u=Object.prototype.toString;function l(e){if(!(this instanceof l))return new l(e);this.options=n.assign({chunkSize:16384,windowBits:0,to:""},e||{});var t=this.options;t.raw&&t.windowBits>=0&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(t.windowBits>=0&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),t.windowBits>15&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new d,this.strm.avail_out=0;var r=i.inflateInit2(this.strm,t.windowBits);if(r!==a.Z_OK)throw new Error(s[r]);this.header=new h,i.inflateGetHeader(this.strm,this.header)}function c(e,t){var r=new l(t);if(r.push(e,!0),r.err)throw r.msg;return r.result}l.prototype.push=function(e,t){var r,s,d,h,l,c,f=this.strm,p=this.options.chunkSize,m=this.options.dictionary,y=!1;if(this.ended)return!1;s=t===~~t?t:!0===t?a.Z_FINISH:a.Z_NO_FLUSH,"string"==typeof e?f.input=o.binstring2buf(e):"[object ArrayBuffer]"===u.call(e)?f.input=new Uint8Array(e):f.input=e,f.next_in=0,f.avail_in=f.input.length;do{if(0===f.avail_out&&(f.output=new n.Buf8(p),f.next_out=0,f.avail_out=p),(r=i.inflate(f,a.Z_NO_FLUSH))===a.Z_NEED_DICT&&m&&(c="string"==typeof m?o.string2buf(m):"[object ArrayBuffer]"===u.call(m)?new Uint8Array(m):m,r=i.inflateSetDictionary(this.strm,c)),r===a.Z_BUF_ERROR&&!0===y&&(r=a.Z_OK,y=!1),r!==a.Z_STREAM_END&&r!==a.Z_OK)return this.onEnd(r),this.ended=!0,!1;f.next_out&&(0!==f.avail_out&&r!==a.Z_STREAM_END&&(0!==f.avail_in||s!==a.Z_FINISH&&s!==a.Z_SYNC_FLUSH)||("string"===this.options.to?(d=o.utf8border(f.output,f.next_out),h=f.next_out-d,l=o.buf2string(f.output,d),f.next_out=h,f.avail_out=p-h,h&&n.arraySet(f.output,f.output,d,h,0),this.onData(l)):this.onData(n.shrinkBuf(f.output,f.next_out)))),0===f.avail_in&&0===f.avail_out&&(y=!0)}while((f.avail_in>0||0===f.avail_out)&&r!==a.Z_STREAM_END);return r===a.Z_STREAM_END&&(s=a.Z_FINISH),s===a.Z_FINISH?(r=i.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===a.Z_OK):s!==a.Z_SYNC_FLUSH||(this.onEnd(a.Z_OK),f.avail_out=0,!0)},l.prototype.onData=function(e){this.chunks.push(e)},l.prototype.onEnd=function(e){e===a.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=n.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},t.Inflate=l,t.inflate=c,t.inflateRaw=function(e,t){return(t=t||{}).raw=!0,c(e,t)},t.ungzip=c},function(e,t,r){"use strict";var i=r(5),n=r(17),o=r(18),a=r(37),s=r(38),d=-2,h=12,u=30;function l(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function c(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new i.Buf16(320),this.work=new i.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function f(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=1,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new i.Buf32(852),t.distcode=t.distdyn=new i.Buf32(592),t.sane=1,t.back=-1,0):d}function p(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,f(e)):d}function m(e,t){var r,i;return e&&e.state?(i=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||t>15)?d:(null!==i.window&&i.wbits!==t&&(i.window=null),i.wrap=r,i.wbits=t,p(e))):d}function y(e,t){var r,i;return e?(i=new c,e.state=i,i.window=null,0!==(r=m(e,t))&&(e.state=null),r):d}var g,_,b=!0;function v(e){if(b){var t;for(g=new i.Buf32(512),_=new i.Buf32(32),t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(s(1,e.lens,0,288,g,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;s(2,e.lens,0,32,_,0,e.work,{bits:5}),b=!1}e.lencode=g,e.lenbits=9,e.distcode=_,e.distbits=5}function w(e,t,r,n){var o,a=e.state;return null===a.window&&(a.wsize=1<=a.wsize?(i.arraySet(a.window,t,r-a.wsize,a.wsize,0),a.wnext=0,a.whave=a.wsize):((o=a.wsize-a.wnext)>n&&(o=n),i.arraySet(a.window,t,r-n,o,a.wnext),(n-=o)?(i.arraySet(a.window,t,r-n,n,0),a.wnext=n,a.whave=a.wsize):(a.wnext+=o,a.wnext===a.wsize&&(a.wnext=0),a.whave>>8&255,r.check=o(r.check,O,2,0),_=0,b=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&_)<<8)+(_>>8))%31){e.msg="incorrect header check",r.mode=u;break}if(8!=(15&_)){e.msg="unknown compression method",r.mode=u;break}if(b-=4,N=8+(15&(_>>>=4)),0===r.wbits)r.wbits=N;else if(N>r.wbits){e.msg="invalid window size",r.mode=u;break}r.dmax=1<>8&1),512&r.flags&&(O[0]=255&_,O[1]=_>>>8&255,r.check=o(r.check,O,2,0)),_=0,b=0,r.mode=3;case 3:for(;b<32;){if(0===y)break e;y--,_+=c[p++]<>>8&255,O[2]=_>>>16&255,O[3]=_>>>24&255,r.check=o(r.check,O,4,0)),_=0,b=0,r.mode=4;case 4:for(;b<16;){if(0===y)break e;y--,_+=c[p++]<>8),512&r.flags&&(O[0]=255&_,O[1]=_>>>8&255,r.check=o(r.check,O,2,0)),_=0,b=0,r.mode=5;case 5:if(1024&r.flags){for(;b<16;){if(0===y)break e;y--,_+=c[p++]<>>8&255,r.check=o(r.check,O,2,0)),_=0,b=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&((x=r.length)>y&&(x=y),x&&(r.head&&(N=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),i.arraySet(r.head.extra,c,p,x,N)),512&r.flags&&(r.check=o(r.check,c,x,p)),y-=x,p+=x,r.length-=x),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===y)break e;x=0;do{N=c[p+x++],r.head&&N&&r.length<65536&&(r.head.name+=String.fromCharCode(N))}while(N&&x>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=h;break;case 10:for(;b<32;){if(0===y)break e;y--,_+=c[p++]<>>=7&b,b-=7&b,r.mode=27;break}for(;b<3;){if(0===y)break e;y--,_+=c[p++]<>>=1)){case 0:r.mode=14;break;case 1:if(v(r),r.mode=20,6===t){_>>>=2,b-=2;break e}break;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=u}_>>>=2,b-=2;break;case 14:for(_>>>=7&b,b-=7&b;b<32;){if(0===y)break e;y--,_+=c[p++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=u;break}if(r.length=65535&_,_=0,b=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(x=r.length){if(x>y&&(x=y),x>g&&(x=g),0===x)break e;i.arraySet(f,c,p,x,m),y-=x,p+=x,g-=x,m+=x,r.length-=x;break}r.mode=h;break;case 17:for(;b<14;){if(0===y)break e;y--,_+=c[p++]<>>=5,b-=5,r.ndist=1+(31&_),_>>>=5,b-=5,r.ncode=4+(15&_),_>>>=4,b-=4,r.nlen>286||r.ndist>30){e.msg="too many length or distance symbols",r.mode=u;break}r.have=0,r.mode=18;case 18:for(;r.have>>=3,b-=3}for(;r.have<19;)r.lens[V[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,M={bits:r.lenbits},P=s(0,r.lens,0,19,r.lencode,0,r.work,M),r.lenbits=M.bits,P){e.msg="invalid code lengths set",r.mode=u;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,S=65535&z,!((T=z>>>24)<=b);){if(0===y)break e;y--,_+=c[p++]<>>=T,b-=T,r.lens[r.have++]=S;else{if(16===S){for(F=T+2;b>>=T,b-=T,0===r.have){e.msg="invalid bit length repeat",r.mode=u;break}N=r.lens[r.have-1],x=3+(3&_),_>>>=2,b-=2}else if(17===S){for(F=T+3;b>>=T)),_>>>=3,b-=3}else{for(F=T+7;b>>=T)),_>>>=7,b-=7}if(r.have+x>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=u;break}for(;x--;)r.lens[r.have++]=N}}if(r.mode===u)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=u;break}if(r.lenbits=9,M={bits:r.lenbits},P=s(1,r.lens,0,r.nlen,r.lencode,0,r.work,M),r.lenbits=M.bits,P){e.msg="invalid literal/lengths set",r.mode=u;break}if(r.distbits=6,r.distcode=r.distdyn,M={bits:r.distbits},P=s(2,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,M),r.distbits=M.bits,P){e.msg="invalid distances set",r.mode=u;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(y>=6&&g>=258){e.next_out=m,e.avail_out=g,e.next_in=p,e.avail_in=y,r.hold=_,r.bits=b,a(e,C),m=e.next_out,f=e.output,g=e.avail_out,p=e.next_in,c=e.input,y=e.avail_in,_=r.hold,b=r.bits,r.mode===h&&(r.back=-1);break}for(r.back=0;A=(z=r.lencode[_&(1<>>16&255,S=65535&z,!((T=z>>>24)<=b);){if(0===y)break e;y--,_+=c[p++]<>U)])>>>16&255,S=65535&z,!(U+(T=z>>>24)<=b);){if(0===y)break e;y--,_+=c[p++]<>>=U,b-=U,r.back+=U}if(_>>>=T,b-=T,r.back+=T,r.length=S,0===A){r.mode=26;break}if(32&A){r.back=-1,r.mode=h;break}if(64&A){e.msg="invalid literal/length code",r.mode=u;break}r.extra=15&A,r.mode=22;case 22:if(r.extra){for(F=r.extra;b>>=r.extra,b-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;A=(z=r.distcode[_&(1<>>16&255,S=65535&z,!((T=z>>>24)<=b);){if(0===y)break e;y--,_+=c[p++]<>U)])>>>16&255,S=65535&z,!(U+(T=z>>>24)<=b);){if(0===y)break e;y--,_+=c[p++]<>>=U,b-=U,r.back+=U}if(_>>>=T,b-=T,r.back+=T,64&A){e.msg="invalid distance code",r.mode=u;break}r.offset=S,r.extra=15&A,r.mode=24;case 24:if(r.extra){for(F=r.extra;b>>=r.extra,b-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=u;break}r.mode=25;case 25:if(0===g)break e;if(x=C-g,r.offset>x){if((x=r.offset-x)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=u;break}x>r.wnext?(x-=r.wnext,E=r.wsize-x):E=r.wnext-x,x>r.length&&(x=r.length),B=r.window}else B=f,E=m-r.offset,x=r.length;x>g&&(x=g),g-=x,r.length-=x;do{f[m++]=B[E++]}while(--x);0===r.length&&(r.mode=21);break;case 26:if(0===g)break e;f[m++]=r.length,g--,r.mode=21;break;case 27:if(r.wrap){for(;b<32;){if(0===y)break e;y--,_|=c[p++]<>>=v=b>>>24,p-=v,0===(v=b>>>16&255))B[o++]=65535&b;else{if(!(16&v)){if(0==(64&v)){b=m[(65535&b)+(f&(1<>>=v,p-=v),p<15&&(f+=E[i++]<>>=v=b>>>24,p-=v,!(16&(v=b>>>16&255))){if(0==(64&v)){b=y[(65535&b)+(f&(1<d){e.msg="invalid distance too far back",r.mode=30;break e}if(f>>>=v,p-=v,k>(v=o-a)){if((v=k-v)>u&&r.sane){e.msg="invalid distance too far back",r.mode=30;break e}if(C=0,x=c,0===l){if(C+=h-v,v2;)B[o++]=x[C++],B[o++]=x[C++],B[o++]=x[C++],w-=3;w&&(B[o++]=x[C++],w>1&&(B[o++]=x[C++]))}else{C=o-k;do{B[o++]=B[C++],B[o++]=B[C++],B[o++]=B[C++],w-=3}while(w>2);w&&(B[o++]=B[C++],w>1&&(B[o++]=B[C++]))}break}}break}}while(i>3,f&=(1<<(p-=w<<3))-1,e.next_in=i,e.next_out=o,e.avail_in=i=1&&0===F[T];T--);if(A>T&&(A=T),0===T)return u[l++]=20971520,u[l++]=20971520,f.bits=1,0;for(B=1;B0&&(0===e||1!==T))return-1;for(z[1]=0,x=1;x852||2===e&&I>592)return 1;for(;;){v=x-U,c[E]b?(w=O[V+c[E]],k=P[M+c[E]]):(w=96,k=0),p=1<>U)+(m-=p)]=v<<24|w<<16|k|0}while(0!==m);for(p=1<>=1;if(0!==p?(N&=p-1,N+=p):N=0,E++,0==--F[x]){if(x===T)break;x=t[r+c[E]]}if(x>A&&(N&g)!==y){for(0===U&&(U=A),_+=B,D=1<<(S=x-U);S+U852||2===e&&I>592)return 1;u[y=N&g]=A<<24|S<<16|_-l|0}}return 0!==N&&(u[_+N]=x-U<<24|64<<16|0),f.bits=A,0}},function(e,t,r){"use strict";e.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},function(e,t,r){var i=r(41);e.exports={TextEncoder:i.TextEncoder,TextDecoder:i.TextDecoder}},function(e,t,r){!function(t){"use strict";function r(e,t,r){return t<=e&&e<=r}function i(e){if(void 0===e)return{};if(e===Object(e))return e;throw TypeError("Could not convert argument to dictionary")}var n=-1;function o(e){this.tokens=[].slice.call(e),this.tokens.reverse()}o.prototype={endOfStream:function(){return!this.tokens.length},read:function(){return this.tokens.length?this.tokens.pop():n},prepend:function(e){if(Array.isArray(e))for(var t=e;t.length;)this.tokens.push(t.pop());else this.tokens.push(e)},push:function(e){if(Array.isArray(e))for(var t=e;t.length;)this.tokens.unshift(t.shift());else this.tokens.unshift(e)}};var a=-1;function s(e,t){if(e)throw TypeError("Decoder error");return t||65533}function d(e){return e=String(e).trim().toLowerCase(),Object.prototype.hasOwnProperty.call(h,e)?h[e]:null}var h={};[{encodings:[{labels:["unicode-1-1-utf-8","utf-8","utf8"],name:"UTF-8"}],heading:"The Encoding"}].forEach((function(e){e.encodings.forEach((function(e){e.labels.forEach((function(t){h[t]=e}))}))}));var u={},l={};function c(e,t){if(!(this instanceof c))throw TypeError("Called as a function. Did you forget 'new'?");e=void 0!==e?String(e):"utf-8",t=i(t),this._encoding=null,this._decoder=null,this._ignoreBOM=!1,this._BOMseen=!1,this._error_mode="replacement",this._do_not_flush=!1;var r=d(e);if(null===r||"replacement"===r.name)throw RangeError("Unknown encoding: "+e);if(!l[r.name])throw Error("Decoder not present. Did you forget to include encoding-indexes.js?");var n=this;return n._encoding=r,Boolean(t.fatal)&&(n._error_mode="fatal"),Boolean(t.ignoreBOM)&&(n._ignoreBOM=!0),n}function f(e,r){if(!(this instanceof f))throw TypeError("Called as a function. Did you forget 'new'?");r=i(r),this._encoding=null,this._encoder=null,this._do_not_flush=!1,this._fatal=Boolean(r.fatal)?"fatal":"replacement";return this._encoding=d("utf-8"),void 0!==e&&"console"in t&&console.warn("TextEncoder constructor called with encoding label, which is ignored."),this}function p(e){var t=e.fatal,i=0,o=0,d=0,h=128,u=191;this.handler=function(e,l){if(l===n&&0!==d)return d=0,s(t);if(l===n)return a;if(0===d){if(r(l,0,127))return l;if(r(l,194,223))d=1,i=31&l;else if(r(l,224,239))224===l&&(h=160),237===l&&(u=159),d=2,i=15&l;else{if(!r(l,240,244))return s(t);240===l&&(h=144),244===l&&(u=143),d=3,i=7&l}return null}if(!r(l,h,u))return i=d=o=0,h=128,u=191,e.prepend(l),s(t);if(h=128,u=191,i=i<<6|63&l,(o+=1)!==d)return null;var c=i;return i=d=o=0,c}}function m(e){e.fatal;this.handler=function(e,t){if(t===n)return a;if(r(t,0,127))return t;var i,o;r(t,128,2047)?(i=1,o=192):r(t,2048,65535)?(i=2,o=224):r(t,65536,1114111)&&(i=3,o=240);for(var s=[(t>>6*i)+o];i>0;){var d=t>>6*(i-1);s.push(128|63&d),i-=1}return s}}Object.defineProperty&&(Object.defineProperty(c.prototype,"encoding",{get:function(){return this._encoding.name.toLowerCase()}}),Object.defineProperty(c.prototype,"fatal",{get:function(){return"fatal"===this._error_mode}}),Object.defineProperty(c.prototype,"ignoreBOM",{get:function(){return this._ignoreBOM}})),c.prototype.decode=function(e,t){var r;r="object"==typeof e&&e instanceof ArrayBuffer?new Uint8Array(e):"object"==typeof e&&"buffer"in e&&e.buffer instanceof ArrayBuffer?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(0),t=i(t),this._do_not_flush||(this._decoder=l[this._encoding.name]({fatal:"fatal"===this._error_mode}),this._BOMseen=!1),this._do_not_flush=Boolean(t.stream);for(var s,d=new o(r),h=[];;){var u=d.read();if(u===n)break;if((s=this._decoder.handler(d,u))===a)break;null!==s&&(Array.isArray(s)?h.push.apply(h,s):h.push(s))}if(!this._do_not_flush){do{if((s=this._decoder.handler(d,d.read()))===a)break;null!==s&&(Array.isArray(s)?h.push.apply(h,s):h.push(s))}while(!d.endOfStream());this._decoder=null}return function(e){var t,r;return t=["UTF-8","UTF-16LE","UTF-16BE"],r=this._encoding.name,-1===t.indexOf(r)||this._ignoreBOM||this._BOMseen||(e.length>0&&65279===e[0]?(this._BOMseen=!0,e.shift()):e.length>0&&(this._BOMseen=!0)),function(e){for(var t="",r=0;r>10),56320+(1023&i)))}return t}(e)}.call(this,h)},Object.defineProperty&&Object.defineProperty(f.prototype,"encoding",{get:function(){return this._encoding.name.toLowerCase()}}),f.prototype.encode=function(e,t){e=e?String(e):"",t=i(t),this._do_not_flush||(this._encoder=u[this._encoding.name]({fatal:"fatal"===this._fatal})),this._do_not_flush=Boolean(t.stream);for(var r,s=new o(function(e){for(var t=String(e),r=t.length,i=0,n=[];i57343)n.push(o);else if(56320<=o&&o<=57343)n.push(65533);else if(55296<=o&&o<=56319)if(i===r-1)n.push(65533);else{var a=t.charCodeAt(i+1);if(56320<=a&&a<=57343){var s=1023&o,d=1023&a;n.push(65536+(s<<10)+d),i+=1}else n.push(65533)}i+=1}return n}(e)),d=[];;){var h=s.read();if(h===n)break;if((r=this._encoder.handler(s,h))===a)break;Array.isArray(r)?d.push.apply(d,r):d.push(r)}if(!this._do_not_flush){for(;(r=this._encoder.handler(s,s.read()))!==a;)Array.isArray(r)?d.push.apply(d,r):d.push(r);this._encoder=null}return new Uint8Array(d)},u["UTF-8"]=function(e){return new m(e)},l["UTF-8"]=function(e){return new p(e)},t.TextEncoder||(t.TextEncoder=f),t.TextDecoder||(t.TextDecoder=c),e.exports&&(e.exports={TextEncoder:t.TextEncoder,TextDecoder:t.TextDecoder})}(this)},function(t,r){t.exports=e},function(e,t,r){"use strict";var i=r(4),n=function(e){this.kdbx=e.kdbx,this.exportXml=e.exportXml||!1};n.prototype.setXmlDate=function(e,t){var r=this.kdbx.header.versionMajor>=4&&!this.exportXml;i.setDate(e,t,r)},e.exports=n},function(e,r){e.exports=t},function(e,t,r){"use strict";var i=r(11),n=r(2),o=r(1),a=r(0),s=r(3);e.exports.decrypt=function(e){return Promise.resolve().then((function(){var t,r=new i(e),d=[],h=0,u=0,l=function(){if(r.getUint32(!0),t=r.readBytes(32),(h=r.getUint32(!0))>0){u+=h;var e=r.readBytes(h);return s.sha256(e).then((function(r){if(a.arrayBufferEquals(r,t))return d.push(e),l();throw new n(o.ErrorCodes.FileCorrupt,"invalid hash block")}))}for(var i=new Uint8Array(u),c=0,f=0;f0){var h=Math.min(1048576,t);t-=h;var u=e.slice(r,r+h);return s.sha256(u).then((function(e){var t=new ArrayBuffer(40),s=new i(t);return s.setUint32(n,!0),s.writeBytes(e),s.setUint32(h,!0),a.push(t),o+=t.byteLength,a.push(u),o+=u.byteLength,n++,r+=h,d()}))}var l=new ArrayBuffer(40);new DataView(l).setUint32(0,n,!0),a.push(l),o+=l.byteLength;for(var c=new Uint8Array(o),f=0,p=0;p0){h+=d;var c=r.readBytes(d);return u(t,s,d,c).then((function(t){if(a.arrayBufferEquals(t,e))return i.push(c),s++,l();throw new n(o.ErrorCodes.FileCorrupt,"invalid hash block")}))}for(var f=new Uint8Array(h),p=0,m=0;m0)return a.push(l),o+=l.byteLength,n++,i+=h,d();for(var u=new Uint8Array(o),c=0,f=0;fthis.nameChanged&&(this._name=e.name,this.nameChanged=e.nameChanged),e.descChanged>this.descChanged&&(this._desc=e.desc,this.descChanged=e.descChanged),e.defaultUserChanged>this.defaultUserChanged&&(this._defaultUser=e.defaultUser,this.defaultUserChanged=e.defaultUserChanged),e.keyChanged>this.keyChanged&&(this.keyChanged=e.keyChanged),e.settingsChanged>this.settingsChanged&&(this.settingsChanged=e.settingsChanged),e.recycleBinChanged>this.recycleBinChanged&&(this._recycleBinEnabled=e.recycleBinEnabled,this._recycleBinUuid=e.recycleBinUuid,this.recycleBinChanged=e.recycleBinChanged),e.entryTemplatesGroupChanged>this.entryTemplatesGroupChanged&&(this._entryTemplatesGroup=e.entryTemplatesGroup,this.entryTemplatesGroupChanged=e.entryTemplatesGroupChanged),Object.keys(e.customData).forEach((function(r){this.customData[r]||t.deleted[r]||(this.customData[r]=e.customData[r])}),this),Object.keys(e.customIcons).forEach((function(r){this.customIcons[r]||t.deleted[r]||(this.customIcons[r]=e.customIcons[r])}),this),this._editState&&this._editState.historyMaxItems||(this.historyMaxItems=e.historyMaxItems),this._editState&&this._editState.historyMaxSize||(this.historyMaxSize=e.historyMaxSize),this._editState&&this._editState.keyChangeRec||(this.keyChangeRec=e.keyChangeRec),this._editState&&this._editState.keyChangeForce||(this.keyChangeForce=e.keyChangeForce),this._editState&&this._editState.mntncHistoryDays||(this.mntncHistoryDays=e.mntncHistoryDays),this._editState&&this._editState.color||(this.color=e.color)},h.create=function(){var e=new Date,t=new h;return t.generator=d.Generator,t.settingsChanged=e,t.mntncHistoryDays=s.Defaults.MntncHistoryDays,t.recycleBinEnabled=!0,t.historyMaxItems=s.Defaults.HistoryMaxItems,t.historyMaxSize=s.Defaults.HistoryMaxSize,t.nameChanged=e,t.descChanged=e,t.defaultUserChanged=e,t.recycleBinChanged=e,t.keyChangeRec=-1,t.keyChangeForce=-1,t.entryTemplatesGroup=new n,t.entryTemplatesGroupChanged=e,t.memoryProtection={title:!1,userName:!1,password:!0,url:!1,notes:!1},t},h.read=function(e,t){for(var r=new h,i=0,n=e.childNodes,o=n.length;ithis.times.lastModTime&&this.copyFrom(t),this.groups=this._mergeCollection(this.groups,t.groups,e),this.entries=this._mergeCollection(this.entries,t.entries,e),this.groups.forEach((function(t){t.merge(e)})),this.entries.forEach((function(t){t.merge(e)})))},u.prototype._mergeCollection=function(e,t,r){var i=[];return e.forEach((function(e){if(!r.deleted[e.uuid]){var t=r.remote[e.uuid];t?t.times.locationChanged<=e.times.locationChanged&&i.push(e):i.push(e)}}),this),t.forEach((function(e,n){if(!r.deleted[e.uuid]){var o=r.objects[e.uuid];if(o&&e.times.locationChanged>o.times.locationChanged)o.parentGroup=this,i.splice(this._findInsertIx(i,t,n),0,o);else if(!o){var a=new e.constructor;a.copyFrom(e),a.parentGroup=this,i.splice(this._findInsertIx(i,t,n),0,a)}}}),this),i},u.prototype._findInsertIx=function(e,t,r){for(var i=e.length,n=-1,o=0;o<=e.length;o++){var a=0,s=r>0?t[r-1].uuid.id:void 0,d=r+10?e[o-1].uuid.id:void 0,u=on&&(i=o,n=a)}return i},u.prototype.copyFrom=function(e){this.uuid=e.uuid,this.name=e.name,this.notes=e.notes,this.icon=e.icon,this.customIcon=e.customIcon,this.times=e.times.clone(),this.expanded=e.expanded,this.defaultAutoTypeSeq=e.defaultAutoTypeSeq,this.enableAutoType=e.enableAutoType,this.enableSearching=e.enableSearching,this.lastTopVisibleEntry=e.lastTopVisibleEntry},u.create=function(e,t){var r=new u;return r.uuid=d.random(),r.icon=o.Icons.Folder,r.times=s.create(),r.name=e,r.parentGroup=t,r.expanded=!0,r.enableAutoType=null,r.enableSearching=null,r.lastTopVisibleEntry=new d,r},u.read=function(e,t,r){for(var i=new u,n=0,o=e.childNodes,a=o.length;n { + return self._newVaultKeyPair(); + }, + }); + }, + + /** + * Handle the deletion of a vault.right field in the vault view properly by + * generating a new master key and re-encrypting everything in the vault to + * deny any future access to the vault. + * + * @private + * @param {Boolean} verify + * @param {Boolean} force + */ + async _reencryptVault(verify = false, force = false) { + const record = this.model.root; + + await this.vault._ensure_keys(); + + const self = this; + const master_key = await this.vault_utils.generate_key(); + const current_key = await this.vault.unwrap(record.data.master_key); + + // This stores the additional changes made to rights, fields, and files + const changes = []; + const problems = []; + + async function reencrypt(model, type) { + // Load the entire data from the database + const records = await self.model.orm.searchRead( + model, + [["vault_id", "=", record.resId]], + ["iv", "value", "name", "entry_name"], + { + context: {vault_reencrypt: true}, + limit: 0, + } + ); + + for (const rec of records) { + const val = await this.vault_utils.sym_decrypt( + current_key, + rec.value, + rec.iv + ); + if (val === null) { + problems.push( + _.str.sprintf( + _t("%s '%s' of entry '%s'"), + type, + rec.name, + rec.entry_name + ) + ); + continue; + } + + const iv = this.vault_utils.generate_iv_base64(); + const encrypted = await this.vault_utils.sym_encrypt( + master_key, + val, + iv + ); + + changes.push({ + id: rec.id, + model: model, + value: encrypted, + iv: iv, + }); + } + } + + this.ui.block(); + try { + // Update the rights. Load without limit + const rights = await self.model.orm.searchRead( + "vault.right", + [["vault_id", "=", record.resId]], + ["public_key"], + {limit: 0} + ); + + for (const right of rights) { + const key = await this.vault.wrap_with(master_key, right.public_key); + + changes.push({ + id: right.id, + model: "vault.right", + key: key, + }); + } + + // Re-encrypt vault.field and vault.file + await reencrypt("vault.field", "Field"); + await reencrypt("vault.file", "File"); + + if (problems.length && !force) { + this.ui.unblock(); + + this.dialogService.add(AlertDialog, { + title: _t("The following entries are broken:"), + body: problems.join("\n"), + }); + } + + if (!verify) { + await rpc("/vault/replace", {data: changes}); + await this.model.root.load(); + } + } finally { + this.ui.unblock(); + } + }, + + /** + * Call the right importer in the import wizard onchange of the content field + * + * @private + */ + async _vaultImportWizard() { + const record = this.model.root; + if (record.resModel !== "vault.import.wizard") return; + + // Try to import the file on the fly and store the compatible JSON in the + // crypted_content field for the python backend + const data = await this.importer.import( + await this.vault.unwrap(record.data.master_key), + record.data.name, + atob(record.data.content) + ); + + if (data) await record.update({crypted_content: JSON.stringify(data)}); + }, + + /** + * Ensure that a vault.right as the shared master_key set + * + * @private + * @param {Object} root + * @param {Object} right + */ + async _vaultEnsureRightKey(root, right) { + if (!root.data.master_key || right.data.key) return; + + const params = {user_id: right.data.user_id[0]}; + const user = await rpc("/vault/public", params); + + if (!user || !user.public_key) throw new TypeError("User has no public key"); + + await right.update({ + key: await this.vault.share(root.data.master_key, user.public_key), + }); + }, + + /** + * Ensures that the master_key of the vault and right lines are set + * + * @private + */ + async _vaultEnsureKeys() { + const root = this.model.root; + if (root.resModel !== "vault") return; + + if (!root.data.master_key) + await root.update({ + master_key: await this.vault.wrap( + await this.vault_utils.generate_key() + ), + }); + + if (root.data.right_ids) + for (const right of root.data.right_ids.records) + await this._vaultEnsureRightKey(root, right); + }, + + /** + * Check the model of the form and call the above functions for the right case + * + * @private + * @param {Object} button + */ + async _vaultAction(button) { + if (!this.vault_utils.supported()) { + await this.dialogService.add(AlertDialog, { + title: _t("Vault is not supported"), + body: _t( + "A secure browser context is required. Please switch to " + + "https or contact your administrator" + ), + }); + return false; + } + + const root = this.model.root; + switch (root.resModel) { + case "res.users": + if (button && button.name === "vault_generate_key") { + await this._vaultRegenerateKey(); + return false; + } + break; + case "vault": + if (button && button.name === "vault_reencrypt") { + await this._reencryptVault(false, true); + return false; + } else if (button && button.name === "vault_verify") { + await this._reencryptVault(true, false); + return false; + } + + await this._vaultEnsureKeys(); + break; + + case "vault.send.wizard": + await this._vaultSendWizard(); + break; + + case "vault.store.wizard": + await this._vaultStoreWizard(); + break; + + case "vault.import.wizard": + await this._vaultImportWizard(); + break; + } + + return true; + }, + + /** + * Add the required rpc service to the controller which will be used to + * get/store information from/to the vault controller + */ + setup() { + this.vault_utils = useService("vault_utils"); + if (this.props.resModel === "vault" && !this.vault_utils.supported()) { + this.props.preventCreate = true; + this.props.preventEdit = true; + } + + super.setup(); + this.ui = useService("ui"); + this.vault = useService("vault"); + this.importer = useService("vault_import"); + }, + + /** + * Hook into the relevant functions + */ + async create() { + const _super = super.create.bind(this); + if (this.model.root.isDirty) await this._vaultAction(); + + const ret = await _super(...arguments); + return ret; + }, + + async onPagerUpdate() { + const _super = super.onPagerUpdate.bind(this); + if (this.model.root.isDirty) await this._vaultAction(); + return await _super(...arguments); + }, + + async saveButtonClicked() { + const _super = super.saveButtonClicked.bind(this); + if (this.model.root.isDirty) await this._vaultAction(); + return await _super(...arguments); + }, + + async discard() { + const _super = super.discard.bind(this); + if (this.model.root.resModel === "vault.entry") + this.model.env.bus.trigger("ENCRYPT_FIELDS"); + return await _super(...arguments); + }, + + async beforeLeave() { + const _super = super.beforeLeave.bind(this); + if (this.model.root.isDirty) await this._vaultAction(); + return await _super(...arguments); + }, + + async beforeUnload() { + const _super = super.beforeUnload.bind(this); + if (this.model.root.isDirty) await this._vaultAction(); + return await _super(...arguments); + }, + + async beforeExecuteActionButton(clickParams) { + const _super = super.beforeExecuteActionButton.bind(this); + if (clickParams.special !== "cancel") { + const _continue = await this._vaultAction(clickParams); + if (!_continue) return false; + } + + return await _super(...arguments); + }, +}); + +patch(ListController.prototype, { + setup() { + super.setup(); + this.vault_utils = useService("vault_utils"); + if (this.props.resModel === "vault" && !this.vault_utils.supported()) + this.props.showButtons = false; + }, +}); diff --git a/vault/static/src/backend/export.esm.js b/vault/static/src/backend/export.esm.js new file mode 100644 index 0000000000..21d114ec17 --- /dev/null +++ b/vault/static/src/backend/export.esm.js @@ -0,0 +1,148 @@ +// © 2021-2024 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import {_t} from "@web/core/l10n/translation"; +import {registry} from "@web/core/registry"; + +const vaultExportService = { + dependencies: ["vault_utils"], + start(env, {vault_utils}) { + // This class handles the export to different formats by using a standardize + // JSON formatted data generated by the python backend. + // + // JSON format description: + // + // Entries are represented as objects with the following attributes + // `name`, `uuid`, `url`, `note` + // Specific fields of the entry. `uuid` is used for updating existing records + // `childs` + // Child entries + // `fields`, `files` + // List of encypted fields/files with `name`, `iv`, and `value` + // + class VaultExporter { + /** + * Encrypt a field of the above format properly for the backend to store. + * The changes are done inplace. + * + * @private + * @param {CryptoKey} master_key + * @param {Object} node + */ + async _export_json_entry(master_key, node) { + const fields = []; + for (const field of node.fields || []) + fields.push({ + name: field.name, + value: await vault_utils.sym_decrypt( + master_key, + field.value, + field.iv + ), + }); + + const files = []; + for (const file of node.files || []) + files.push({ + name: file.name, + value: await vault_utils.sym_decrypt( + master_key, + file.value, + file.iv + ), + }); + + const childs = []; + for (const entry of node.childs || []) + childs.push(await this._export_json_entry(master_key, entry)); + + return { + name: node.name || "", + uuid: node.uuid || null, + url: node.url || "", + note: node.note || "", + childs: childs, + fields: fields, + files: files, + }; + } + + /** + * Decrypt the data fro the JSON export. + * + * @private + * @param {CryptoKey} master_key + * @param {Object} data + * @returns the encrypted entry for the database + */ + async _export_json_data(master_key, data) { + const result = []; + for (const node of data) + result.push(await this._export_json_entry(master_key, node)); + return JSON.stringify(result); + } + + /** + * Export using JSON format. The database is stored in the `data` field of the JSON + * type and is an encrypted JSON object. For the encryption the needed encryption + * parameter `iv`, `salt` and `iterations` are stored in the file. + * This will add `iv` to fields and files and encrypt the `value` + * + * @private + * @param {CryptoKey} master_key + * @param {String} data + * @returns the encrypted entry for the database + */ + async _export_json(master_key, data) { + // Get the password for the exported file from the user + const askpass = await vault_utils.askpass( + _t("Please enter the password for the database") + ); + let password = askpass.password || ""; + if (askpass.keyfile) + password += await vault_utils.digest( + vault_utils.toBinary(askpass.keyfile) + ); + + const iv = vault_utils.generate_iv_base64(); + const salt = vault_utils.generate_bytes(vault_utils.SaltLength).buffer; + const iterations = vault_utils.Derive.iterations; + const key = await vault_utils.derive_key(password, salt, iterations); + + // Unwrap the master key and decrypt the entries + const content = await this._export_json_data( + master_key, + JSON.parse(data) + ); + return { + type: "encrypted", + iv: iv, + salt: vault_utils.toBase64(salt), + data: await vault_utils.sym_encrypt(key, content, iv), + iterations: iterations, + }; + } + + /** + * The main export functions which checks the file ending and calls the right function + * to handle the rest of the export + * + * @private + * @param {CryptoKey} master_key + * @param {String} filename + * @param {String} content + * @returns the data importable by the backend or false on error + */ + async export(master_key, filename, content) { + if (!vault_utils.supported()) return false; + + if (filename.endsWith(".json")) + return await this._export_json(master_key, content); + return false; + } + } + return new VaultExporter(); + }, +}; + +registry.category("services").add("vault_export", vaultExportService); diff --git a/vault/static/src/backend/fields/templates.xml b/vault/static/src/backend/fields/templates.xml new file mode 100644 index 0000000000..8f3c59e043 --- /dev/null +++ b/vault/static/src/backend/fields/templates.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+ + + + +
+
+
diff --git a/vault/static/src/backend/user_menu.esm.js b/vault/static/src/backend/user_menu.esm.js new file mode 100644 index 0000000000..9921f31394 --- /dev/null +++ b/vault/static/src/backend/user_menu.esm.js @@ -0,0 +1,27 @@ +// © 2021-2022 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import {_t} from "@web/core/l10n/translation"; +import {registry} from "@web/core/registry"; +import {user} from "@web/core/user"; + +export function vaultPreferencesItem(env) { + return { + type: "item", + id: "key_management", + description: _t("Key Management"), + callback: async function () { + const actionDescription = await env.services.orm.call( + "res.users", + "action_get_vault" + ); + actionDescription.res_id = user.userId; + env.services.action.doAction(actionDescription); + }, + sequence: 55, + }; +} + +registry + .category("user_menuitems") + .add("vault_key_management", vaultPreferencesItem, {force: true}); diff --git a/vault/static/src/backend/vault.esm.js b/vault/static/src/backend/vault.esm.js new file mode 100644 index 0000000000..b16a4e0584 --- /dev/null +++ b/vault/static/src/backend/vault.esm.js @@ -0,0 +1,429 @@ +// © 2021-2024 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import {_t} from "@web/core/l10n/translation"; +import {registry} from "@web/core/registry"; +import {rpc} from "@web/core/network/rpc"; +import {session} from "@web/session"; + +// Database name on the browser +const Database = "vault"; + +const indexedDB = + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB || + window.shimIndexedDB; + +// Expiration time of the vault store entries +const Expiration = 15 * 60 * 1000; + +const vaultService = { + dependencies: ["vault_utils"], + start(env, {vault_utils}) { + /** + * Ask the user to enter a password using a dialog and put the password together + * + * @param {Boolean} confirm + * @returns password + */ + async function askpassword(confirm = false) { + const askpass = await vault_utils.askpass( + _t("Please enter the password for your private key"), + {confirm} + ); + + let password = askpass.password || ""; + if (askpass.keyfile) { + password += await vault_utils.digest( + vault_utils.toBinary(askpass.keyfile) + ); + } + return password; + } + + class Vault { + /** + * Check if the user actually has keys otherwise generate them on init + * + * @override + */ + constructor() { + const self = this; + + function waitAndCheck() { + if (!vault_utils.supported()) return null; + + if (odoo.isReady) self._initialize_keys(); + else setTimeout(waitAndCheck, 500); + } + + setTimeout(waitAndCheck, 500); + } + + /** + * Generate a new key pair and export to database and object store + */ + async generate_keys() { + this.keys = await vault_utils.generate_key_pair(); + this.time = new Date(); + + if (!(await this._export_to_database())) + throw Error(_t("Failed to export the keys to the database")); + + await this._export_to_store(); + } + + /** + * Check if export to database is required due to key migration + * + * @private + * @param {String} password + */ + async _check_key_migration(password = null) { + if (!this.version) await this._export_to_database(password); + if (this.iterations < vault_utils.Derive.iterations) + await this._export_to_database(password); + } + + /** + * Lazy initialization of the keys which is not fully loading the keys + * into the javascript but ensures that keys exist in the database to + * to be loaded + * + * @private + */ + async _initialize_keys() { + // Get the uuid of the currently active keys from the database + this.uuid = await this._check_database(); + if (this.uuid) { + // If the object store has the keys it's done + if (await this._import_from_store()) return; + + // Otherwise an import from the database and export to the object store + // is needed + if (await this._import_from_database()) { + await this._export_to_store(); + return true; + } + + // This should be silent because it would influence the entire workflow + console.error("Failed to import the keys from the database"); + return false; + } + + // There are no keys in the database which means we have to generate them + return await this.generate_keys(); + } + + /** + * Ensure that the keys are available + * + * @private + */ + async _ensure_keys() { + // If the object store has the keys it's done + if (this.uuid && !this.time) await this._import_from_store(); + + // Check if the keys expired + const now = new Date(); + if (this.time && now - this.time <= Expiration) return; + + // Keys expired means that we have to get them again + this.keys = this.time = null; + + // Clear the object store first + const store = await this._get_object_store(); + store.clear(); + + // Import the keys from the database + if (!(await this._import_from_database())) + throw Error(_t("Failed to import keys from database")); + + // Store the imported keys in the object store for the next calls + if (!(await this._export_to_store())) + throw Error(_t("Failed to export keys to object store")); + + return; + } + + /** + * Get the private key and check if the keys expired + * + * @returns the private key of the user + */ + async get_private_key() { + await this._ensure_keys(); + return this.keys.privateKey; + } + + /** + * Get the public key and check if the keys expired + * + * @returns the public key of the user + */ + async get_public_key() { + await this._ensure_keys(); + return this.keys.publicKey; + } + + /** + * Open the indexed DB and return object store using promise + * + * @private + * @returns a promise + */ + _get_object_store() { + return new Promise((resolve, reject) => { + const open = indexedDB.open(Database, 1); + open.onupgradeneeded = function () { + const db = open.result; + db.createObjectStore(Database, {keyPath: "id"}); + }; + + open.onerror = function (event) { + reject(`error opening database ${event.target.errorCode}`); + }; + + open.onsuccess = function () { + const db = open.result; + const tx = db.transaction(Database, "readwrite"); + + resolve(tx.objectStore(Database)); + + tx.oncomplete = function () { + db.close(); + }; + }; + }); + } + + /** + * Open the object store and extract the keys using the id + * + * @private + * @param {String} uuid + * @returns the result from the object store or false + */ + async _get_keys(uuid) { + const self = this; + return new Promise((resolve, reject) => { + self._get_object_store().then((store) => { + const request = store.get(uuid); + request.onerror = function (event) { + reject(`error opening database ${event.target.errorCode}`); + }; + request.onsuccess = function () { + resolve(request.result); + }; + }); + }); + } + + /** + * Check if the keys exist in the database + * + * @returns the uuid of the currently active keys or false + */ + async _check_database() { + const params = await rpc("/vault/keys/get"); + if (Object.keys(params).length && params.uuid) return params.uuid; + return false; + } + + /** + * Check if the keys exist in the store + * + * @private + * @param {String} uuid + * @returns if the keys are in the object store + */ + async _check_store(uuid) { + if (!uuid) return false; + + const result = await this._get_keys(uuid); + return Boolean(result && result.keys); + } + + /** + * Import the keys from the indexed DB + * + * @private + * @returns if the import from the object store succeeded + */ + async _import_from_store() { + const data = await this._get_keys(this.uuid); + if (data) { + this.keys = data.keys; + this.time = data.time; + return true; + } + return false; + } + + /** + * Export the current keys to the indexed DB + * + * @private + * @returns true + */ + async _export_to_store() { + const keys = {id: this.uuid, keys: this.keys, time: this.time}; + const store = await this._get_object_store(); + store.put(keys); + return true; + } + + /** + * Export the key pairs to the backends + * + * @private + * @param {String} password + * @returns if the export to the database succeeded + */ + async _export_to_database(password = null) { + // Generate salt for the user key + this.salt = vault_utils.generate_bytes(vault_utils.SaltLength).buffer; + this.iterations = vault_utils.Derive.iterations; + this.version = 1; + + // Wrap the private key with the master key of the user + this.iv = vault_utils.generate_bytes(vault_utils.IVLength); + + // Request the password from the user and derive the user key + const pass = await vault_utils.derive_key( + password || (await askpassword(true)), + this.salt, + this.iterations + ); + + // Export the private key wrapped with the master key + const private_key = await vault_utils.export_private_key( + await this.get_private_key(), + pass, + this.iv + ); + + // Export the public key + const public_key = await vault_utils.export_public_key( + await this.get_public_key() + ); + + const params = { + public: public_key, + private: private_key, + iv: vault_utils.toBase64(this.iv), + iterations: this.iterations, + salt: vault_utils.toBase64(this.salt), + version: this.version, + }; + + // Export to the server + const response = await rpc("/vault/keys/store", params); + if (response) { + this.uuid = response; + return true; + } + + console.error("Failed to export keys to database"); + return false; + } + + /** + * Import the keys from the backend and decrypt the private key + * + * @private + * @returns if the import succeeded + */ + async _import_from_database() { + const params = await rpc("/vault/keys/get"); + if (Object.keys(params).length) { + this.salt = vault_utils.fromBase64(params.salt); + this.iterations = params.iterations; + this.version = params.version || 0; + + // Request the password from the user and derive the user key + const raw_password = await askpassword(false); + let password = raw_password; + + // Compatibility + if (!this.version) password = session.username + "|" + password; + + const pass = await vault_utils.derive_key( + password, + this.salt, + this.iterations + ); + + this.keys = { + publicKey: await vault_utils.load_public_key(params.public), + privateKey: await vault_utils.load_private_key( + params.private, + pass, + params.iv + ), + }; + + this.time = new Date(); + this.uuid = params.uuid; + + this._check_key_migration(raw_password); + return true; + } + return false; + } + + /** + * Wrap the master key with the own public key + * + * @param {CryptoKey} master_key + * @returns wrapped master key + */ + async wrap(master_key) { + return await vault_utils.wrap(master_key, await this.get_public_key()); + } + + /** + * Wrap the master key with a public key given as string + * + * @param {CryptoKey} master_key + * @param {String} public_key + * @returns wrapped master key + */ + async wrap_with(master_key, public_key) { + const pub_key = await vault_utils.load_public_key(public_key); + return await vault_utils.wrap(master_key, pub_key); + } + + /** + * Unwrap the master key with the own private key + * + * @param {CryptoKey} master_key + * @returns unwrapped master key + */ + async unwrap(master_key) { + return await vault_utils.unwrap( + master_key, + await this.get_private_key() + ); + } + + /** + * Share a wrapped master key by unwrapping with own private key and wrapping with + * another key + * + * @param {String} master_key + * @param {String} public_key + * @returns wrapped master key + */ + async share(master_key, public_key) { + const key = await this.unwrap(master_key); + return await this.wrap_with(key, public_key); + } + } + return new Vault(); + }, +}; + +registry.category("services").add("vault", vaultService); diff --git a/vault/static/src/backend/vault.scss b/vault/static/src/backend/vault.scss new file mode 100644 index 0000000000..d8f61fbcd0 --- /dev/null +++ b/vault/static/src/backend/vault.scss @@ -0,0 +1,13 @@ +.o_vault_inbox { + white-space: pre-wrap; +} + +.o_field_cell .o_vault .o_vault_buttons { + float: right; + position: relative; + z-index: 10; +} + +.o_vault .o_input { + width: auto; +} diff --git a/vault/static/src/common/utils.esm.js b/vault/static/src/common/utils.esm.js new file mode 100644 index 0000000000..5d53aeb816 --- /dev/null +++ b/vault/static/src/common/utils.esm.js @@ -0,0 +1,415 @@ +// © 2021-2024 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +const CryptoAPI = window.crypto?.subtle; + +// Some basic constants used for the entire vaults +const Hash = "SHA-512"; +const HashLength = 10; +const IVLength = 12; +const SaltLength = 32; + +const Asymmetric = { + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: Hash, +}; +const Derive = { + name: "PBKDF2", + iterations: 600001, +}; +const Symmetric = { + name: "AES-GCM", + length: 256, +}; + +/** + * Checks if the CryptoAPI is available and the vault feature supported + * + * @returns if vault is supported + */ +function supported() { + return Boolean(CryptoAPI); +} + +/** + * Converts an ArrayBuffer to an ASCII string + * + * @param {ArrayBuffer} buffer + * @returns the data converted to a string + */ +function toBinary(buffer) { + if (!buffer) return ""; + const chars = Array.from(new Uint8Array(buffer)).map((b) => String.fromCharCode(b)); + return chars.join(""); +} + +/** + * Converts an ASCII string to an ArrayBuffer + * + * @param {String} binary + * @returns the data converted to an ArrayBuffer + */ +function fromBinary(binary) { + const len = binary.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i); + return bytes.buffer; +} + +/** + * Converts an ArrayBuffer to a Base64 encoded string + * + * @param {ArrayBuffer} buffer + * @returns Base64 string + */ +function toBase64(buffer) { + if (!buffer) return ""; + const chars = Array.from(new Uint8Array(buffer)).map((b) => String.fromCharCode(b)); + return btoa(chars.join("")); +} + +/** + * Converts an Base64 encoded string to an ArrayBuffer + * + * @param {String} base64 + * @returns the data converted to an ArrayBuffer + */ +function fromBase64(base64) { + if (!base64) { + const bytes = new Uint8Array(0); + return bytes.buffer; + } + const binary_string = atob(base64); + const len = binary_string.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) bytes[i] = binary_string.charCodeAt(i); + return bytes.buffer; +} + +/** + * Generate random bytes used for salts or IVs + * + * @param {int} length + * @returns an array with length random bytes + */ +function generate_bytes(length) { + const buf = new Uint8Array(length); + window.crypto.getRandomValues(buf); + return buf; +} + +/** + * Generate random bytes used for salts or IVs encoded as base64 + * + * @returns base64 string + */ +function generate_iv_base64() { + return toBase64(generate_bytes(IVLength)); +} + +/** + * Generate a random secret with specific characters + * + * @param {int} length + * @param {String} characters + * @returns base64 string + */ +function generate_secret(length, characters) { + let result = ""; + const len = characters.length; + for (const k of generate_bytes(length)) { + result += characters[Math.floor((len * k) / 256)]; + } + return result; +} + +/** + * Generate a symmetric key for encrypting and decrypting + * + * @returns symmetric key + */ +async function generate_key() { + return await CryptoAPI.generateKey(Symmetric, true, ["encrypt", "decrypt"]); +} + +/** + * Generate an asymmetric key pair for encrypting, decrypting, wrapping and unwrapping + * + * @returns asymmetric key + */ +async function generate_key_pair() { + return await CryptoAPI.generateKey(Asymmetric, true, [ + "wrapKey", + "unwrapKey", + "decrypt", + "encrypt", + ]); +} + +/** + * Generate a hash of the given data + * + * @param {String} data + * @returns base64 encoded hash of the data + */ +async function digest(data) { + const encoder = new TextEncoder(); + return toBase64(await CryptoAPI.digest(Hash, encoder.encode(data))); +} + +/** + * Derive a key using the given data, salt and iterations using PBKDF2 + * + * @param {String} data + * @param {String} salt + * @param {int} iterations + * @returns the derived key + */ +async function derive_key(data, salt, iterations) { + const enc = new TextEncoder(); + const material = await CryptoAPI.importKey( + "raw", + enc.encode(data), + Derive.name, + false, + ["deriveBits", "deriveKey"] + ); + + return await CryptoAPI.deriveKey( + { + name: Derive.name, + salt: salt, + iterations: iterations, + hash: Hash, + }, + material, + Symmetric, + false, + ["wrapKey", "unwrapKey", "encrypt", "decrypt"] + ); +} + +/** + * Encrypt the data using a public key + * + * @param {CryptoKey} public_key + * @param {String} data + * @returns the encrypted data + */ +async function asym_encrypt(public_key, data) { + if (!data) return data; + const enc = new TextEncoder(); + return toBase64( + await CryptoAPI.encrypt({name: Asymmetric.name}, public_key, enc.encode(data)) + ); +} + +/** + * Decrypt the data using the own private key + * + * @param {CryptoKey} private_key + * @param {String} crypted + * @returns the decrypted data + */ +async function asym_decrypt(private_key, crypted) { + if (!crypted) return crypted; + const dec = new TextDecoder(); + return dec.decode( + await CryptoAPI.decrypt( + {name: Asymmetric.name}, + private_key, + fromBase64(crypted) + ) + ); +} + +/** + * Symmetrically encrypt the data using a master key + * + * @param {CryptoKey} key + * @param {String} data + * @param {String} iv + * @returns the encrypted data + */ +async function sym_encrypt(key, data, iv) { + if (!data) return data; + const hash = await digest(data); + const enc = new TextEncoder(); + return toBase64( + await CryptoAPI.encrypt( + {name: Symmetric.name, iv: fromBase64(iv), tagLength: 128}, + key, + enc.encode(hash.slice(0, HashLength) + data) + ) + ); +} + +/** + * Symmetrically decrypt the data using a master key + * + * @param {CryptoKey} key + * @param {String} crypted + * @param {String} iv + * @returns the decrypted data + */ +async function sym_decrypt(key, crypted, iv) { + if (!crypted) return crypted; + try { + const dec = new TextDecoder(); + const message = dec.decode( + await CryptoAPI.decrypt( + {name: Symmetric.name, iv: fromBase64(iv), tagLength: 128}, + key, + fromBase64(crypted) + ) + ); + const data = message.slice(HashLength); + const hash = await digest(data); + // Compare the hash and return if integer + if (hash.slice(0, HashLength) === message.slice(0, HashLength)) return data; + + console.error("Invalid data hash"); + // Wrong hash + return null; + } catch (err) { + console.error(err); + return null; + } +} + +/** + * Load a public key + * + * @param {String} public_key + * @returns the public key as CryptoKey + */ +async function load_public_key(public_key) { + return await CryptoAPI.importKey("spki", fromBase64(public_key), Asymmetric, true, [ + "wrapKey", + "encrypt", + ]); +} + +/** + * Load a private key + * + * @param {String} private_key + * @param {CryptoKey} key + * @param {String} iv + * @returns the private key as CryptoKey + */ +async function load_private_key(private_key, key, iv) { + return await CryptoAPI.unwrapKey( + "pkcs8", + fromBase64(private_key), + key, + {name: Symmetric.name, iv: fromBase64(iv), tagLength: 128}, + Asymmetric, + true, + ["unwrapKey", "decrypt"] + ); +} + +/** + * Export a public key in spki format + * + * @param {CryptoKey} public_key + * @returns the public key as string + */ +async function export_public_key(public_key) { + return toBase64(await CryptoAPI.exportKey("spki", public_key)); +} + +/** + * Export a private key in pkcs8 format + * + * @param {String} private_key + * @param {CryptoKey} key + * @param {String} iv + * @returns the public key as CryptoKey + */ +async function export_private_key(private_key, key, iv) { + return toBase64( + await CryptoAPI.wrapKey("pkcs8", private_key, key, { + name: Symmetric.name, + iv: iv, + tagLength: 128, + }) + ); +} + +/** + * Wrap the master key with the own public key + * + * @param {CryptoKey} key + * @param {CryptoKey} public_key + * @returns wrapped master key + */ +async function wrap(key, public_key) { + return toBase64(await CryptoAPI.wrapKey("raw", key, public_key, Asymmetric)); +} + +/** + * Unwrap the master key with the own private key + * + * @param {CryptoKey} key + * @param {CryptoKey} private_key + * @returns unwrapped master key + */ +async function unwrap(key, private_key) { + return await CryptoAPI.unwrapKey( + "raw", + fromBase64(key), + private_key, + Asymmetric, + Symmetric, + true, + ["encrypt", "decrypt"] + ); +} + +/** + * Capitalize each word of the string + * + * @param {String} s + * @returns capitalized string + */ +function capitalize(s) { + return s.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase()); +} + +export default { + Asymmetric, + Derive, + Hash, + HashLength, + IVLength, + SaltLength, + Symmetric, + // Crypto + supported, + digest, + derive_key, + generate_bytes, + generate_iv_base64, + generate_secret, + generate_key, + generate_key_pair, + asym_encrypt, + asym_decrypt, + sym_encrypt, + sym_decrypt, + load_public_key, + load_private_key, + export_public_key, + export_private_key, + wrap, + unwrap, + // Utils + capitalize, + toBase64, + fromBase64, + toBinary, + fromBinary, +}; diff --git a/vault/static/src/common/vault_utils_service.esm.js b/vault/static/src/common/vault_utils_service.esm.js new file mode 100644 index 0000000000..1ffafd3912 --- /dev/null +++ b/vault/static/src/common/vault_utils_service.esm.js @@ -0,0 +1,142 @@ +import {Component, onMounted, useRef, useState} from "@odoo/owl"; +import {Dialog} from "@web/core/dialog/dialog"; +import {_t} from "@web/core/l10n/translation"; +import {registry} from "@web/core/registry"; +import utils from "./utils.esm"; + +export class AskPassDialog extends Component { + static template = "vault.AskPassDialog"; + static components = {Dialog}; + + setup() { + this.state = useState({ + password: "", + confirm: "", + error: "", + }); + this.keyfileInput = useRef("keyfileInput"); + } + + async onConfirm() { + const {confirm} = this.props; + const password = this.state.password; + let keyfileContent = null; + const input = this.keyfileInput.el; + if (input && input.files && input.files[0]) { + const file = input.files[0]; + const text = await file.text(); + keyfileContent = utils.fromBinary(text); + } + if (!password && !keyfileContent) { + this.state.error = _t("Missing password"); + return; + } + if (confirm && password && this.state.confirm !== password) { + this.state.error = _t("The passwords aren't matching"); + return; + } + this.props.onResolve({ + password, + keyfile: keyfileContent, + }); + this.props.close(); + } + + onCancel() { + this.props.onReject(_t("Cancelled")); + this.props.close(); + } +} + +export class GeneratePassDialog extends Component { + static template = "vault.GeneratePassDialog"; + static components = {Dialog}; + + setup() { + this.state = useState({ + length: 15, + big: true, + small: true, + digits: true, + special: false, + password: "", + error: "", + }); + + onMounted(() => this.generate()); + } + + generate() { + let characters = ""; + if (this.state.big) characters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + if (this.state.small) characters += "abcdefghijklmnopqrstuvwxyz"; + if (this.state.digits) characters += "0123456789"; + if (this.state.special) characters += "!?$%&/()[]{}|<>,;.:-_#+*\\"; + + if (!characters) { + this.state.password = ""; + this.state.error = _t("Select at least one character set"); + return; + } + + this.state.error = ""; + this.state.password = utils.generate_secret(this.state.length, characters); + } + + onOptionsChange() { + this.generate(); + } + + onCancel() { + this.props.onReject(_t("Cancelled")); + this.props.close(); + } + + onConfirm() { + if (!this.state.password) { + this.state.error = _t("Missing password"); + return; + } + this.props.onResolve(this.state.password); + this.props.close(); + } +} + +export const vaultUtilsService = { + dependencies: ["dialog"], + + start(env, {dialog}) { + function askpass(title, options = {}) { + const props = { + title, + confirm: Boolean(options.confirm), + }; + return new Promise((resolve, reject) => { + dialog.add(AskPassDialog, { + ...props, + onResolve: resolve, + onReject: reject, + }); + }); + } + + function generate_pass(title, options = {}) { + const props = {title, ...options}; + return new Promise((resolve, reject) => { + dialog.add(GeneratePassDialog, { + ...props, + onResolve: resolve, + onReject: reject, + }); + }); + } + + return { + ...utils, + askpass, + generate_pass, + }; + }, +}; + +registry.category("services").add("vault_utils", vaultUtilsService); diff --git a/vault/static/src/frontend/inbox.esm.js b/vault/static/src/frontend/inbox.esm.js new file mode 100644 index 0000000000..b010eccf79 --- /dev/null +++ b/vault/static/src/frontend/inbox.esm.js @@ -0,0 +1,95 @@ +// © 2021-2024 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import utils from "../common/utils.esm"; + +const data = {}; +let key = false; +let iv = false; + +const fields = [ + "key", + "iv", + "public", + "encrypted", + "secret", + "encrypted_file", + "filename", + "secret_file", + "submit", +]; + +function toggle_required(element, value) { + if (value) element.setAttribute("required", "required"); + else element.removeAttribute("required"); +} + +// Encrypt the value and store it in the right input field +async function encrypt_and_store(value, target) { + if (!utils.supported()) return false; + + // Find all the possible elements which are needed + for (const id of fields) if (!data[id]) data[id] = document.getElementById(id); + + // We expect a public key here otherwise we can't procceed + if (!data.public.value) return; + + const public_key = await utils.load_public_key(data.public.value); + + // Create a new key if not already present + if (!key) { + key = await utils.generate_key(); + data.key.value = await utils.wrap(key, public_key); + } + + // Create a new IV if not already present + if (!iv) { + iv = utils.generate_iv_base64(); + data.iv.value = iv; + } + + // Encrypt the value symmetrically and store it in the field + const val = await utils.sym_encrypt(key, value, iv); + data[target].value = val; + return Boolean(val); +} + +document.getElementById("secret").onchange = async function () { + if (!utils.supported()) return false; + + if (!this.value) return; + + const required = await encrypt_and_store(this.value, "encrypted"); + toggle_required(data.secret, required); + toggle_required(data.secret_file, !required); + data.submit.removeAttribute("disabled"); +}; + +document.getElementById("secret_file").onchange = async function () { + if (!utils.supported()) return false; + + if (!this.files.length) return; + + const file = this.files[0]; + const reader = new FileReader(); + let content = null; + + const promise = new Promise((resolve) => { + reader.onload = () => { + if (reader.result.indexOf(",") >= 0) content = reader.result.split(",")[1]; + resolve(); + }; + }); + + reader.readAsDataURL(file); + + await promise; + + if (!content) return; + + const required = await encrypt_and_store(content, "encrypted_file"); + toggle_required(data.secret, !required); + toggle_required(data.secret_file, required); + data.filename.value = file.name; + data.submit.removeAttribute("disabled"); +}; diff --git a/vault/static/tests/vault_tests.esm.js b/vault/static/tests/vault_tests.esm.js new file mode 100644 index 0000000000..0bcc23ead4 --- /dev/null +++ b/vault/static/tests/vault_tests.esm.js @@ -0,0 +1,202 @@ +// © 2021 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import {makeTestEnv} from "@web/../tests/helpers/mock_env"; + +let utils = null; + +QUnit.module( + "vault", + { + before: async function () { + const env = await makeTestEnv({activateMockServer: true}); + utils = env.services.vault_utils; + utils.askpass = async function () { + return { + password: "test", + keyfile: "", + }; + }; + }, + }, + function () { + function is_keypair(keys, assert) { + assert.equal(keys.publicKey instanceof CryptoKey, true); + assert.equal(keys.publicKey.type, "public"); + assert.equal(keys.privateKey instanceof CryptoKey, true); + assert.equal(keys.privateKey.type, "private"); + } + + QUnit.test("vault: Test conversion utils", async function (assert) { + assert.expect(7); + + let text = "hello world"; + let buf = utils.fromBinary(text); + assert.equal(true, buf instanceof ArrayBuffer); + assert.equal(text, utils.toBinary(buf)); + assert.equal("", utils.toBinary(false)); + + text = "ImhlbGxvIHdvcmxkIg=="; + buf = utils.fromBase64(text); + assert.equal(true, buf instanceof ArrayBuffer); + assert.equal(text, utils.toBase64(buf)); + assert.equal("", utils.toBase64(false)); + + assert.equal("Hello World", utils.capitalize("hello world")); + }); + + QUnit.test("vault: Test generation utils", async function (assert) { + assert.expect(12); + + let data = utils.generate_bytes(5); + assert.equal(true, data instanceof Uint8Array); + assert.equal(data.length, 5); + data = utils.generate_bytes(10); + assert.equal(data.length, 10); + + data = utils.generate_iv_base64(); + assert.equal(typeof data, "string"); + assert.notEqual(data, utils.generate_iv_base64()); + + data = await utils.generate_key(); + assert.equal(true, data instanceof CryptoKey); + + data = await utils.generate_key_pair(); + is_keypair(data, assert); + + data = utils.generate_secret(10, "01"); + assert.equal(data.length, 10); + let valid = true; + for (const c of data) if ("01".indexOf(c) < 0) valid = false; + assert.equal(valid, true); + }); + + QUnit.test("vault: Test asymmetric encryption", async function (assert) { + assert.expect(2); + const text = "hello world"; + const key = await utils.generate_key_pair(); + + const crypted = await utils.asym_encrypt(key.publicKey, text); + assert.equal("string", typeof crypted); + assert.strictEqual(text, await utils.asym_decrypt(key.privateKey, crypted)); + }); + + QUnit.test("vault: Test symmetric encryption", async function (assert) { + assert.expect(2); + const text = "hello world"; + const key = await utils.generate_key(); + const iv = utils.generate_iv_base64(); + + const crypted = await utils.sym_encrypt(key, text, iv); + assert.equal("string", typeof crypted); + assert.strictEqual(text, await utils.sym_decrypt(key, crypted, iv)); + }); + + QUnit.test("vault: Test import/export", async function (assert) { + assert.expect(3); + + const key = await utils.generate_key_pair(); + let exported = await utils.export_public_key(key.publicKey); + let tmp = await utils.load_public_key(exported); + assert.deepEqual(key.publicKey, tmp); + + const iv = utils.generate_bytes(10); + const salt = utils.generate_bytes(10); + const wrapper = await utils.derive_key("test", salt, 4000); + exported = await utils.export_private_key(key.privateKey, wrapper, iv); + tmp = await utils.load_private_key(exported, wrapper, utils.toBase64(iv)); + assert.deepEqual(key.privateKey, tmp); + + const master_key = await utils.generate_key(); + exported = await utils.wrap(master_key, key.publicKey); + tmp = await utils.unwrap(exported, key.privateKey); + assert.deepEqual(master_key, tmp); + }); + + QUnit.test("vault: Test vault class", async function (assert) { + assert.expect(12); + + const env = await makeTestEnv({activateMockServer: true}); + var vault = env.services.vault; + + await vault._initialize_keys(); + is_keypair(vault.keys, assert); + + vault.keys = undefined; + await vault._import_from_store(); + is_keypair(vault.keys, assert); + + vault.keys = undefined; + await vault._import_from_database(); + is_keypair(vault.keys, assert); + }); + + QUnit.test("vault: Importer/exporter", async function (assert) { + // The exporter won't skip empty keys + const child = { + uuid: "42a", + note: "test note child", + name: "test child", + url: "child.example.org", + fields: [], + files: [], + childs: [], + }; + + const data = { + type: "raw", + data: [ + child, + { + uuid: "42", + note: "test note", + name: "test name", + url: "test.example.org", + fields: [ + {name: "a", value: "Hello World"}, + {name: "secret", value: "dlrow olleh"}, + ], + files: [], + childs: [child, child], + }, + child, + ], + }; + + assert.expect(2); + + const env = await makeTestEnv({activateMockServer: true}); + var Exporter = env.services.vault_export; + var Importer = env.services.vault_import; + var vault = env.services.vault; + + await vault._initialize_keys(); + + const master_key = await utils.generate_key(); + const importer = new Importer(); + const imported = await importer.import( + master_key, + "test.json", + JSON.stringify(data) + ); + + const exporter = new Exporter(); + const exported = await exporter.export( + master_key, + "test.json", + JSON.stringify(imported) + ); + assert.equal(exported.type, "encrypted"); + + const pass = await utils.derive_key( + "test", + utils.fromBase64(exported.salt), + exported.iterations + ); + + const tmp = JSON.parse( + await utils.sym_decrypt(pass, exported.data, exported.iv) + ); + assert.deepEqual(tmp, data.data); + }); + } +); diff --git a/vault/tests/__init__.py b/vault/tests/__init__.py new file mode 100644 index 0000000000..59ea2f2ef1 --- /dev/null +++ b/vault/tests/__init__.py @@ -0,0 +1,11 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + test_controller, + test_log, + test_rights, + test_user, + test_vault, + test_widgets, +) diff --git a/vault/tests/test_controller.py b/vault/tests/test_controller.py new file mode 100644 index 0000000000..3cf770ae2d --- /dev/null +++ b/vault/tests/test_controller.py @@ -0,0 +1,217 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging +from unittest.mock import MagicMock + +from odoo.tools import mute_logger + +from odoo.addons.base.tests.common import BaseCommon +from odoo.addons.website.tools import MockRequest + +from ..controllers import main + +_logger = logging.getLogger(__name__) + + +class TestController(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.controller = main.Controller() + + cls.user = cls.env["res.users"].create( + {"login": "test", "email": "test@test", "name": "test"} + ) + cls.user.inbox_token = "42" + cls.user.keys.current = False + cls.key = cls.env["res.users.key"].create( + { + "user_id": cls.user.id, + "public": "a public key", + "salt": "42", + "iv": "2424", + "iterations": 4000, + "private": "24", + "current": True, + } + ) + cls.inbox = cls.env["vault.inbox"].create( + { + "user_id": cls.user.id, + "name": "Inbox", + "key": "4", + "iv": "1", + "secret": "old secret", + "secret_file": "old file", + "accesses": 100, + } + ) + + @mute_logger("odoo.sql_db", "odoo.addons.vault.controllers.main") + def test_vault_inbox(self): + def return_context(template, context): + self.assertEqual(template, "vault.inbox") + return json.dumps(context) + + def load(response): + return json.loads(response.data) + + with MockRequest(self.env) as request_mock: + request_mock.render = return_context + response = load(self.controller.vault_inbox("")) + self.assertIn("error", response) + + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertNotIn("error", response) + self.assertEqual(response["public"], self.user.active_key.public) + + # Try to eliminate each error step by step + request_mock.httprequest.method = "POST" + request_mock.params = {} + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertIn("error", response) + + request_mock.params["name"] = "test" + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertIn("error", response) + + request_mock.params.update( + {"encrypted": "secret", "encrypted_file": "file"} + ) + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertIn("error", response) + + request_mock.params["filename"] = "filename" + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertIn("error", response) + + self.assertEqual(self.inbox.secret, "old secret") + self.assertEqual(self.inbox.secret_file, b"old file") + + # Store something successfully + request_mock.params.update({"iv": "iv", "key": "key"}) + response = load(self.controller.vault_inbox(self.inbox.token)) + self.assertNotIn("error", response) + self.assertEqual(self.inbox.secret, "secret") + self.assertEqual(self.inbox.secret_file, b"file") + + # Test a duplicate inbox + self.inbox.copy().token = self.inbox.token + response = load(self.controller.vault_inbox(self.inbox.token)) + self.assertIn("error", response) + + def raise_error(*args, **kwargs): + raise TypeError() + + # Catch internal errors + request_mock.httprequest.remote_addr = "127.0.0.1" + self.patch(type(self.env["vault.inbox"]), "store_in_inbox", raise_error) + response = load(self.controller.vault_inbox(self.user.inbox_token)) + self.assertIn("error", response) + + @mute_logger("odoo.sql_db") + def test_vault_public(self): + with MockRequest(self.env): + no_key = self.env["res.users"].create( + {"login": "keyless", "email": "test@test", "name": "test"} + ) + + response = self.controller.vault_public(user_id=no_key.id) + self.assertEqual(response, {}) + + response = self.controller.vault_public(user_id=self.user.id) + self.assertEqual(response["public_key"], self.key.public) + + @mute_logger("odoo.sql_db") + def test_vault_replace(self): + with MockRequest(self.env): + vault = self.env["vault"].create({"name": "Vault"}) + right = vault.right_ids[:1] + entry = self.env["vault.entry"].create( + {"name": "Test Entry", "vault_id": vault.id} + ) + field = self.env["vault.field"].create( + {"entry_id": entry.id, "name": "Test", "value": "hello"} + ) + file = self.env["vault.file"].create( + {"entry_id": entry.id, "name": "Test", "value": b"hello"} + ) + right.write({"key": "invalid"}) + + self.controller.vault_replace(None) + self.assertEqual(field.value, "hello") + self.assertEqual(file.value, b"hello") + + vault.reencrypt_required = True + self.controller.vault_replace( + [ + {"model": field._name, "id": field.id, "value": "test"}, + {"model": file._name, "id": file.id, "value": "test"}, + {"model": right._name, "id": right.id, "key": "changed"}, + ] + ) + self.assertEqual(field.value, "test") + self.assertEqual(file.value, b"test") + self.assertEqual(right.key, "changed") + self.assertFalse(vault.reencrypt_required) + + @mute_logger("odoo.sql_db") + def test_vault_store(self): + with MockRequest(self.env): + mock = MagicMock() + self.patch(type(self.env["res.users.key"]), "store", mock) + self.controller.vault_store_keys() + mock.assert_called_once() + + @mute_logger("odoo.sql_db") + def test_vault_keys_get(self): + with MockRequest(self.env): + mock = MagicMock() + self.patch(type(self.env["res.users"]), "get_vault_keys", mock) + self.controller.vault_get_keys() + mock.assert_called_once() + + @mute_logger("odoo.sql_db") + def test_vault_right_keys(self): + with MockRequest(self.env): + self.assertFalse(self.controller.vault_get_right_keys()) + + # New vault with user as owner and only right + vault = self.env["vault"].create({"name": "Vault"}) + + response = self.controller.vault_get_right_keys() + self.assertEqual(response, {vault.uuid: vault.right_ids.key}) + + @mute_logger("odoo.sql_db") + def test_vault_store_right_key(self): + with MockRequest(self.env): + vault = self.env["vault"].create({"name": "Vault"}) + + self.controller.vault_store_right_keys(None) + + self.controller.vault_store_right_keys({vault.uuid: "new key"}) + self.assertEqual(vault.right_ids.key, "new key") + + @mute_logger("odoo.sql_db") + def test_vault_inbox_keys(self): + with MockRequest(self.env): + self.assertFalse(self.controller.vault_get_inbox()) + + inbox = self.inbox.copy({"user_id": self.env.uid}) + + response = self.controller.vault_get_inbox() + self.assertEqual(response, {inbox.token: inbox.key}) + + @mute_logger("odoo.sql_db") + def test_vault_store_inbox_key(self): + with MockRequest(self.env): + inbox = self.inbox.copy({"user_id": self.env.uid}) + inbox.user_id = self.env.user + + self.controller.vault_store_inbox(None) + + self.controller.vault_store_inbox({inbox.token: "new key"}) + self.assertEqual(inbox.key, "new key") diff --git a/vault/tests/test_inbox.py b/vault/tests/test_inbox.py new file mode 100644 index 0000000000..a095b782be --- /dev/null +++ b/vault/tests/test_inbox.py @@ -0,0 +1,115 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime +from uuid import uuid4 + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestShare(BaseCommon): + def test_user_inbox(self): + user = self.env["res.users"].create( + {"login": "test", "email": "test@test", "name": "test"} + ) + + user.action_new_inbox_token() + + model = self.env["res.users"] + token = user.inbox_token + + self.assertEqual(user, model.find_user_of_inbox(token)) + self.assertIn(token, user.inbox_link) + + user.inbox_enabled = False + self.assertEqual(model, model.find_user_of_inbox(token)) + + user.action_new_inbox_token() + self.assertNotEqual(user.inbox_token, token) + + def test_inbox(self): + model = self.env["vault.inbox"] + user = self.env.user + vals = { + "name": f"Inbox {user.name}", + "secret": "secret", + "iv": "iv", + "user": user, + "key": "key", + "secret_file": "", + "filename": "", + } + + # Should create a new inbox for the user + inbox = model.store_in_inbox(**vals) + self.assertEqual(inbox.secret, "secret") + self.assertEqual(inbox.accesses, 0) + self.assertIn(inbox.token, inbox.inbox_link) + + # No change because of no accesses left + vals["secret"] = "new secret" + inbox.store_in_inbox(**vals) + self.assertEqual(inbox.secret, "secret") + self.assertEqual(inbox.accesses, 0) + + # Change expected because 5 accesses left + inbox.accesses = 5 + inbox.store_in_inbox(**vals) + self.assertEqual(inbox.secret, "new secret") + self.assertEqual(inbox.accesses, 4) + + # No change because expired + vals["secret"] = "newer secret" + inbox.expiration = datetime(1970, 1, 1) + inbox.store_in_inbox(**vals) + self.assertEqual(inbox.secret, "new secret") + self.assertEqual(inbox.accesses, 4) + + # Search for shares + self.assertEqual(inbox, model.find_inbox(inbox.token)) + self.assertEqual(model, model.find_inbox(uuid4())) + + def test_send_wizard(self): + user = self.env.user + wiz = self.env["vault.send.wizard"].create( + { + "name": uuid4(), + "iv": "iv", + "key_user": "key", + "key": "k", + "secret": uuid4(), + "user_id": user.id, + } + ) + + # Create a new inbox + wiz.action_send() + self.assertTrue(self.env["vault.inbox"].search([("name", "=", wiz.name)])) + + def test_store_wizard(self): + vault = self.env["vault"].create({"name": "Vault"}) + + entry = self.env["vault.entry"].create({"vault_id": vault.id, "name": "Entry"}) + + wiz = self.env["vault.store.wizard"].create( + { + "vault_id": vault.id, + "entry_id": entry.id, + "name": uuid4(), + "iv": "iv", + "key": "k", + "secret": uuid4(), + "secret_temporary": "temp", + "model": "vault.field", + } + ) + + vault.right_ids.write({"key": uuid4()}) + self.assertEqual(wiz.master_key, vault.right_ids.key) + + wiz.action_store() + + self.assertEqual(wiz.name, entry.field_ids.name) diff --git a/vault/tests/test_log.py b/vault/tests/test_log.py new file mode 100644 index 0000000000..38cd7a38e6 --- /dev/null +++ b/vault/tests/test_log.py @@ -0,0 +1,41 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestLog(BaseCommon): + def test_not_implemeneted(self): + with self.assertRaises(NotImplementedError): + self.env["vault.abstract"].log_entry("test") + + with self.assertRaises(NotImplementedError): + self.env["vault.abstract"].log_info("test") + + with self.assertRaises(NotImplementedError): + self.env["vault.abstract"].log_warn("test") + + with self.assertRaises(NotImplementedError): + self.env["vault.abstract"].log_error("test") + + def test_log_created(self): + vault = self.env["vault"].create({"name": "Vault"}) + entry = self.env["vault.entry"].create({"vault_id": vault.id, "name": "Entry"}) + + vault.log_ids.unlink() + + vault.log_info("info") + self.assertEqual(vault.log_ids.mapped("state"), ["info"]) + self.assertEqual(entry.log_ids.mapped("state"), []) + + entry.log_warn("warn") + self.assertEqual(vault.log_ids.mapped("state"), ["info", "warn"]) + self.assertEqual(entry.log_ids.mapped("state"), ["warn"]) + + entry.log_error("error") + self.assertEqual(vault.log_ids.mapped("state"), ["info", "warn", "error"]) + self.assertEqual(entry.log_ids.mapped("state"), ["warn", "error"]) diff --git a/vault/tests/test_rights.py b/vault/tests/test_rights.py new file mode 100644 index 0000000000..16f22eafb8 --- /dev/null +++ b/vault/tests/test_rights.py @@ -0,0 +1,159 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.exceptions import AccessError +from odoo.tests import new_test_user + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestAccessRights(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.user = new_test_user( + cls.env, + login="test-vault-user", + ) + cls.vault = cls.env["vault"].create({"name": "Vault"}) + cls.entry = cls.env["vault.entry"].create( + {"vault_id": cls.vault.id, "name": "Entry"} + ) + cls.field = cls.env["vault.field"].create( + {"entry_id": cls.entry.id, "name": "Field", "value": "Value"} + ) + cls.vault.right_ids.write({"key": "Owner"}) + + def test_vault_reencrypt(self): + right = self.env["vault.right"].create( + { + "vault_id": self.vault.id, + "user_id": self.user.id, + "perm_create": False, + } + ) + + assert not self.vault.reencrypt_required + right.unlink() + assert self.vault.reencrypt_required + + def test_public_key(self): + key = self.env["res.users.key"].create( + { + "user_id": self.vault.user_id.id, + "public": "a public key", + "salt": "42", + "iv": "2424", + "iterations": 4000, + "private": "24", + } + ) + self.assertTrue(self.vault.right_ids.public_key) + self.assertEqual(key.public, self.vault.right_ids.public_key) + + def test_owner_access(self): + # The owner can always access despite the permissions + for obj in [self.field, self.entry, self.vault]: + obj.name = "Owned" + + right = self.vault.right_ids + right.perm_write = False + obj.name = "Owned" + + right.perm_delete = False + obj.unlink() + + def test_no_create(self): + self.env["vault.right"].create( + { + "vault_id": self.vault.id, + "user_id": self.user.id, + "perm_create": False, + } + ) + + for obj in [self.field, self.entry, self.vault]: + with self.assertRaises(AccessError): + obj.with_user(self.user).check_access("create") + + def test_no_right(self): + # No right defined for test user means access denied + for obj in [self.field, self.entry, self.vault]: + with self.assertRaises(AccessError): + self.assertTrue(obj.with_user(self.user).read()) + + with self.assertRaises(AccessError): + obj.with_user(self.user).name = "Owned" + + with self.assertRaises(AccessError): + obj.with_user(self.user).unlink() + + def test_no_permission(self): + # Defined right but no write permission means access denied + self.env["vault.right"].create( + { + "vault_id": self.vault.id, + "user_id": self.user.id, + "perm_create": False, + "perm_write": False, + "perm_delete": False, + } + ) + for obj in [self.field, self.entry, self.vault]: + self.assertTrue(obj.with_user(self.user).read()) + + with self.assertRaises(AccessError): + obj.with_user(self.user).name = "Owned" + + with self.assertRaises(AccessError): + obj.with_user(self.user).unlink() + + def test_granted(self): + # Granted write permission allows writing + self.env["vault.right"].create( + { + "vault_id": self.vault.id, + "user_id": self.user.id, + "perm_write": True, + "perm_delete": True, + } + ) + for obj in [self.field, self.entry, self.vault]: + self.assertTrue(obj.with_user(self.user).read()) + + obj.with_user(self.user).name = "Owned" + obj.with_user(self.user).unlink() + + def test_owner_share(self): + self.env["vault.right"].create( + {"vault_id": self.vault.id, "user_id": self.user.id} + ) + + def test_user_share_no_right(self): + # No right defined means AccessError + with self.assertRaises(AccessError): + self.env["vault.right"].with_user(self.user).create( + {"vault_id": self.vault.id, "user_id": 2} + ) + + def test_user_share_no_permission(self): + # Created right but no permission to share + right = self.env["vault.right"].create( + {"vault_id": self.vault.id, "user_id": self.user.id, "perm_share": False} + ) + + with self.assertRaises(AccessError): + right.with_user(self.user).create({"vault_id": self.vault.id, "user_id": 2}) + + def test_user_share_granted(self): + # Granted permission to share + right = self.env["vault.right"].create( + {"vault_id": self.vault.id, "user_id": self.user.id, "perm_share": True} + ) + right.with_user(self.user).create({"vault_id": self.vault.id, "user_id": 2}) + + right.unlink() diff --git a/vault/tests/test_user.py b/vault/tests/test_user.py new file mode 100644 index 0000000000..1da84a4b23 --- /dev/null +++ b/vault/tests/test_user.py @@ -0,0 +1,60 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestShare(BaseCommon): + def test_user_inbox(self): + user = self.env["res.users"].create( + {"login": "test", "email": "test@test", "name": "test"} + ) + + user.action_new_inbox_token() + + model = self.env["res.users"] + token = user.inbox_token + + self.assertEqual(user, model.find_user_of_inbox(token)) + self.assertIn(token, user.inbox_link) + + user.inbox_enabled = False + self.assertEqual(model, model.find_user_of_inbox(token)) + + user.action_new_inbox_token() + self.assertNotEqual(user.inbox_token, token) + + def test_user_key_management(self): + action = self.env.ref("vault.action_res_users_keys") + + self.assertEqual(action.id, self.env["res.users"].action_get_vault()["id"]) + + def test_invalidation(self): + self.env["res.users.key"].store( + 40000, "invalid", "invalid", "invalid", "invalid", 42 + ) + self.assertTrue(self.env.user.keys.filtered("current")) + + vault = self.env["vault"].create({"name": "Test"}) + self.assertTrue(vault.right_ids) + + inbox = self.env["vault.inbox"].create( + { + "name": "Inbox Test", + "secret": "secret", + "iv": "iv", + "user_id": self.env.uid, + "key": "key", + "secret_file": "", + "filename": "", + } + ) + + self.env.user.action_invalidate_key() + self.assertFalse(self.env.user.keys.filtered("current")) + self.assertFalse(inbox.exists()) + self.assertFalse(vault.right_ids.exists()) diff --git a/vault/tests/test_vault.py b/vault/tests/test_vault.py new file mode 100644 index 0000000000..9c50ff4d9a --- /dev/null +++ b/vault/tests/test_vault.py @@ -0,0 +1,168 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# Copyright 2022 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime + +from odoo.exceptions import ValidationError + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestVault(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.vault = cls.env["vault"].create({"name": "Vault"}) + cls.entry = cls.env["vault.entry"].create( + {"vault_id": cls.vault.id, "name": "Entry"} + ) + cls.child = cls.env["vault.entry"].create( + {"vault_id": cls.vault.id, "name": "Child", "parent_id": cls.entry.id} + ) + + def test_entry_path(self): + self.assertEqual(self.entry.complete_name, "Entry") + self.assertEqual(self.child.complete_name, "Entry / Child") + + def test_wizard_actions(self): + values = self.child.action_open_import_wizard() + self.assertEqual(values["context"]["default_parent_id"], self.child.id) + + values = self.vault.action_open_import_wizard() + self.assertNotIn("default_parent_id", values["context"]) + + values = self.child.action_open_export_wizard() + self.assertEqual(values["context"]["default_entry_id"], self.child.id) + + values = self.vault.action_open_export_wizard() + self.assertNotIn("default_entry_id", values["context"]) + + def test_master_key(self): + right = self.vault.right_ids + self.assertEqual(self.vault.master_key, right.master_key) + + self.vault.master_key = "test" + self.assertEqual(right.key, "test") + + def test_share_public_key(self): + key = self.env["res.users.key"].create( + { + "user_id": self.vault.user_id.id, + "public": "a public key", + "salt": "42", + "iv": "2424", + "iterations": 4000, + "private": "24", + } + ) + + expected = {"user": 1, "public": key.public} + self.assertIn(expected, self.vault.share_public_keys()) + + def test_keys(self): + key = self.env["res.users.key"].create( + { + "user_id": self.vault.user_id.id, + "public": "a public key", + "salt": "42", + "iv": "2424", + "iterations": 4000, + "private": "24", + } + ) + + self.assertEqual(set("0123456789abcdef:"), set(key.fingerprint)) + + key.public = "" + self.assertEqual(key.fingerprint, False) + + def test_store_keys(self): + model = self.env["res.users.key"] + + # Raise some errors because of wrong parameters + with self.assertRaises(ValidationError): + model.store(1, "iv", "private", "public", 42, 42) + + with self.assertRaises(ValidationError): + model.store(3000, "iv", "private", "public", "salt", 42) + + with self.assertRaises(ValidationError): + model.store(4000, "iv", "private", "public", "salt", "abc") + + # Actually store a new key + uuid = model.store(4000, "iv", "private", "public", "salt", 42) + rec = model.search([("uuid", "=", uuid)]) + self.assertEqual(rec.private, "private") + self.assertTrue(rec.current) + + # Don't store the same again + uuid = model.store(4000, "iv", "private", "public", "salt", 42) + self.assertFalse(uuid) + + # Store a new one and disable the old one + uuid = model.store(4000, "iv", "more private", "public", "salt", 42) + self.assertFalse(rec.current) + + rec = model.search([("uuid", "=", uuid)]) + self.assertEqual(rec.private, "more private") + self.assertTrue(rec.current) + + # Try to extract the public key again + user_id = self.env["res.users"].search([], limit=1, order="id DESC").id + public = model.extract_public_key(user_id + 1) + self.assertFalse(public) + public = model.extract_public_key(self.env.uid) + self.assertEqual(public, "public") + + def test_vault_keys(self): + keys = self.env.user.get_vault_keys() + self.assertEqual(keys, {}) + + data = { + "user_id": self.env.user.id, + "public": "a public key", + "salt": "42", + "iv": "2424", + "iterations": 4000, + "private": "24", + } + self.env["res.users.key"].create(data) + + keys = self.env.user.get_vault_keys() + for key in ["private", "public", "iv", "salt", "iterations"]: + self.assertEqual(keys[key], data[key]) + + def test_vault_entry_recursion(self): + child = self.env["vault.entry"].create( + {"vault_id": self.vault.id, "name": "Entry", "parent_id": self.entry.id} + ) + + with self.assertRaises(ValidationError): + self.entry.parent_id = child.id + + def test_search_expired(self): + entry = self.env["vault.entry"] + self.assertEqual(entry._search_expired("in", []), []) + + domain = entry._search_expired("=", True) + self.assertEqual(domain[0][:2], ("expire_date", "<")) + self.assertIsInstance(domain[0][2], datetime) + + domain = entry._search_expired("!=", False) + self.assertEqual(domain[0][:2], ("expire_date", "<")) + self.assertIsInstance(domain[0][2], datetime) + + domain = entry._search_expired("=", False) + self.assertTrue(domain[0] == "|") + self.assertIn(("expire_date", "=", False), domain) + self.assertTrue(any(("expire_date", ">=") == d[:2] for d in domain)) + + def test_vault_entry_search_panel_limit(self): + res = self.entry.search_panel_select_range("parent_id") + total_items = self.env["vault.entry"].search_count([("child_ids", "!=", False)]) + self.assertEqual(len(res["values"]), total_items) diff --git a/vault/tests/test_widgets.py b/vault/tests/test_widgets.py new file mode 100644 index 0000000000..3b4d93684b --- /dev/null +++ b/vault/tests/test_widgets.py @@ -0,0 +1,158 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging +from uuid import uuid4 + +from odoo.exceptions import UserError + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +TestChild = { + "uuid": "42a", + "note": "test note child", + "name": "test child", + "url": "child.example.org", + "fields": [], + "files": [], + "childs": [], +} + +TestData = [ + { + "uuid": "42b", + "note": "test note child", + "name": "don't import", + "url": "child.example.org", + "fields": [], + "files": [], + "childs": [], + }, + TestChild, + { + "uuid": "42", + "note": "test note", + "name": "test name", + "url": "test.example.org", + "fields": [ + {"name": "a", "value": "Hello World", "iv": "abcd"}, + {"name": "secret", "value": "dlrow olleh", "iv": "abcd"}, + {"name": "secret", "value": "dlrow olle", "iv": "abcd"}, + ], + "files": [ + {"name": "a", "value": "Hello World", "iv": "abcd"}, + {"name": "secret", "value": "dlrow olleh", "iv": "abcd"}, + {}, + ], + "childs": [ + { + "uuid": "42aa", + "note": "test note subchild", + "name": "test subchild", + "url": "subchild.example.org", + "fields": [], + "files": [], + "childs": [], + }, + TestChild, + ], + }, + TestChild, +] + + +class TestWidgets(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.vault = cls.env["vault"].create({"name": "Vault"}) + cls.entry = cls.env["vault.entry"].create( + {"vault_id": cls.vault.id, "name": "Entry"} + ) + + def test_path_generation(self): + wiz = self.env["vault.import.wizard"].create( + {"vault_id": self.vault.id, "crypted_content": json.dumps(TestData)} + ) + wiz._onchange_content() + + paths = wiz.path.search([("uuid", "=", wiz.uuid)]).mapped("name") + + self.assertEqual(len(paths), 6) + self.assertIn("test name / test child", paths) + self.assertIn("test child", paths) + self.assertIn("test name", paths) + + def test_import(self): + uuid = uuid4() + path = self.env["vault.import.wizard.path"].create( + {"name": "test", "uuid": uuid} + ) + + wiz = self.env["vault.import.wizard"].create( + { + "vault_id": self.vault.id, + "crypted_content": json.dumps(TestData), + "path": path.id, + "uuid": uuid, + } + ) + + wiz.action_import() + + # We have duplicates + uuids = {"42", "42a", "42aa", self.entry.uuid} + self.assertSetEqual(set(self.vault.entry_ids.mapped("uuid")), uuids) + + # Creation is depth-first which will cause the 42a to move up + self.assertEqual(self.vault.entry_ids.mapped("child_ids.uuid"), ["42aa"]) + + # This will cause an overwrite of the field + domain = [("entry_id.uuid", "=", "42"), ("name", "=", "secret")] + rec = self.env["vault.field"].search(domain) + self.assertEqual(rec.mapped("value"), ["dlrow olle"]) + + # Field with missing keys should fail + with self.assertRaises(UserError): + TestChild["fields"].append({"name": "12", "value": "eulav"}) + wiz.crypted_content = json.dumps([TestChild]) + wiz.action_import() + + def test_export(self): + child = self.env["vault.entry"].create( + {"vault_id": self.vault.id, "name": "Child", "parent_id": self.entry.id} + ) + + second = self.env["vault.entry"].create( + {"vault_id": self.vault.id, "name": "second"} + ) + + wiz = self.env["vault.export.wizard"].create({"vault_id": self.vault.id}) + + # Export without entry should export entire vault + wiz._onchange_content() + entries = json.loads(wiz.content) + self.assertEqual({e["uuid"] for e in entries}, {second.uuid, self.entry.uuid}) + self.assertEqual(len(entries), 2) + + wiz.entry_id = self.entry + + # Export the entire tree + wiz._onchange_content() + entries = json.loads(wiz.content) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]["uuid"], self.entry.uuid) + self.assertEqual(entries[0]["childs"][0]["uuid"], child.uuid) + + # Skip exporting childs + wiz.include_childs = False + wiz._onchange_content() + entries = json.loads(wiz.content) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]["uuid"], self.entry.uuid) + self.assertEqual(len(entries[0]["childs"]), 0) diff --git a/vault/views/menuitems.xml b/vault/views/menuitems.xml new file mode 100644 index 0000000000..e1234153fb --- /dev/null +++ b/vault/views/menuitems.xml @@ -0,0 +1,63 @@ + + + + Vault + vault + list,form + + + + All Entries + vault.entry + list,form + {'search_default_vault': 1} + + + + + Inbox + vault.inbox + list,form + + + + Rights + vault.right + list + + + + + + + + + diff --git a/vault/views/res_config_settings_views.xml b/vault/views/res_config_settings_views.xml new file mode 100644 index 0000000000..5c4ed8d341 --- /dev/null +++ b/vault/views/res_config_settings_views.xml @@ -0,0 +1,33 @@ + + + + res.config.settings.view.form + res.config.settings + + + + + + + + + + + + + + + + + + diff --git a/vault/views/res_users_views.xml b/vault/views/res_users_views.xml new file mode 100644 index 0000000000..3bfd9bd007 --- /dev/null +++ b/vault/views/res_users_views.xml @@ -0,0 +1,77 @@ + + + + res.users.key + + + + + + + + + + res.users.key + +
+ + + + + +
+
+
+ + + res.users + +
+ + + + + + + + +
+
+
+
+
+ + + Manage my keys + ir.actions.act_window + res.users + new + form + + + + + form + + + +
diff --git a/vault/views/templates.xml b/vault/views/templates.xml new file mode 100644 index 0000000000..2be894810a --- /dev/null +++ b/vault/views/templates.xml @@ -0,0 +1,93 @@ + + + + diff --git a/vault/views/vault_entry_views.xml b/vault/views/vault_entry_views.xml new file mode 100644 index 0000000000..57e6be91d2 --- /dev/null +++ b/vault/views/vault_entry_views.xml @@ -0,0 +1,229 @@ + + + + vault.entry + + + + + + + + + + + vault.entry + + + + + + + + + + + + vault.entry + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + vault.entry + primary + 100 + + + + 0 + + + + + + vault.entry.search + vault.entry + + + + + + + + + + + + + + + + +
diff --git a/vault/views/vault_field_views.xml b/vault/views/vault_field_views.xml new file mode 100644 index 0000000000..fdd133d81b --- /dev/null +++ b/vault/views/vault_field_views.xml @@ -0,0 +1,36 @@ + + + + vault.field + +
+ + + + + + + + + + + +
+
+
+ + + vault.field + + + + + + + + + + + + +
diff --git a/vault/views/vault_file_views.xml b/vault/views/vault_file_views.xml new file mode 100644 index 0000000000..d79e8b1686 --- /dev/null +++ b/vault/views/vault_file_views.xml @@ -0,0 +1,36 @@ + + + + vault.file + +
+ + + + + + + + + + + +
+
+
+ + + vault.file + + + + + + + + + + + + +
diff --git a/vault/views/vault_inbox_views.xml b/vault/views/vault_inbox_views.xml new file mode 100644 index 0000000000..461edec8c2 --- /dev/null +++ b/vault/views/vault_inbox_views.xml @@ -0,0 +1,51 @@ + + + + vault.inbox + + + + + + + + + + vault.inbox + +
+ + + + + + + + + + + + + + + +
+
+
+
diff --git a/vault/views/vault_log_views.xml b/vault/views/vault_log_views.xml new file mode 100644 index 0000000000..0b5e4a44a6 --- /dev/null +++ b/vault/views/vault_log_views.xml @@ -0,0 +1,18 @@ + + + + vault.log + + + + + + + + + + diff --git a/vault/views/vault_right_views.xml b/vault/views/vault_right_views.xml new file mode 100644 index 0000000000..d4b02b5987 --- /dev/null +++ b/vault/views/vault_right_views.xml @@ -0,0 +1,73 @@ + + + + vault.right + + + + + + + + + + + + + + + + + vault.right + +
+ + + + + + + + + +
+
+
+ + + vault.right + + + + + + + + + + + + + + vault.right.overview.search + vault.right + + + + + + + + + + + +
diff --git a/vault/views/vault_views.xml b/vault/views/vault_views.xml new file mode 100644 index 0000000000..d50a322de8 --- /dev/null +++ b/vault/views/vault_views.xml @@ -0,0 +1,106 @@ + + + + vault.search + vault + + + + + + + + + + Entries + vault.entry + list,form + [("vault_id", "=", active_id)] + { + "default_vault_id": active_id, + "searchpanel_default_vault_id": active_id} + + + + + + vault + + + + + + + + + + + vault + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/vault/wizards/__init__.py b/vault/wizards/__init__.py new file mode 100644 index 0000000000..4059c96dcc --- /dev/null +++ b/vault/wizards/__init__.py @@ -0,0 +1,9 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + vault_export_wizard, + vault_import_wizard, + vault_send_wizard, + vault_store_wizard, +) diff --git a/vault/wizards/vault_export_wizard.py b/vault/wizards/vault_export_wizard.py new file mode 100644 index 0000000000..4d4110b01e --- /dev/null +++ b/vault/wizards/vault_export_wizard.py @@ -0,0 +1,71 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging +from datetime import datetime + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class ExportWizard(models.TransientModel): + _name = "vault.export.wizard" + _description = "Export wizard for vaults" + + vault_id = fields.Many2one("vault", "Vault") + entry_id = fields.Many2one( + "vault.entry", "Entries", domain="[('vault_id', '=', vault_id)]" + ) + master_key = fields.Char(related="vault_id.master_key") + name = fields.Char(default=lambda self: self._default_name()) + content = fields.Binary("Download", attachment=False) + include_childs = fields.Boolean(default=True) + + @api.onchange("vault_id", "entry_id") + def _onchange_content(self): + for rec in self.with_context(skip_log=True): + rec.content = self._export_content( + rec.vault_id, + rec.entry_id, + rec.include_childs, + ) + + def _default_name(self): + return datetime.now().strftime("database-%Y%m%d-%H%M.json") + + def _export_content(self, vault=None, entry=None, include_childs=False): + if entry: + entries = entry + elif vault: + entries = vault.entry_ids.filtered_domain([("parent_id", "=", False)]) + else: + return json.dumps([]) + + data = [self._export_entry(x, include_childs) for x in entries] + return json.dumps(data) + + @api.model + def _export_field(self, rec): + def ensure_string(x): + return x.decode() if isinstance(x, bytes) else x + + return {f: ensure_string(rec[f]) for f in ["name", "iv", "value"]} + + @api.model + def _export_entry(self, entry, include_childs=False): + if include_childs: + childs = [self._export_entry(x, include_childs) for x in entry.child_ids] + else: + childs = [] + + return { + "uuid": entry.uuid, + "name": entry.name, + "note": entry.note, + "url": entry.url, + "fields": entry.field_ids.mapped(self._export_field), + "files": entry.file_ids.mapped(self._export_field), + "childs": childs, + } diff --git a/vault/wizards/vault_export_wizard.xml b/vault/wizards/vault_export_wizard.xml new file mode 100644 index 0000000000..71c043569a --- /dev/null +++ b/vault/wizards/vault_export_wizard.xml @@ -0,0 +1,29 @@ + + + + vault.export.wizard + +
+ + + + + + + + + + + +
+
+
+
+
+
diff --git a/vault/wizards/vault_import_wizard.py b/vault/wizards/vault_import_wizard.py new file mode 100644 index 0000000000..a273349d59 --- /dev/null +++ b/vault/wizards/vault_import_wizard.py @@ -0,0 +1,134 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging +from uuid import uuid4 + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class ImportWizardPath(models.TransientModel): + _name = "vault.import.wizard.path" + _description = "Import wizard path for vaults" + + name = fields.Char(required=True) + uuid = fields.Char(required=True) + + +class ImportWizard(models.TransientModel): + _name = "vault.import.wizard" + _description = "Import wizard for vaults" + + vault_id = fields.Many2one("vault", "Vault") + parent_id = fields.Many2one( + "vault.entry", + "Parent Entry", + domain="[('vault_id', '=', vault_id)]", + ) + master_key = fields.Char(related="vault_id.master_key") + name = fields.Char() + content = fields.Binary("Database", attachment=False) + crypted_content = fields.Char() + uuid = fields.Char(default=lambda self: uuid4()) + + path = fields.Many2one( + "vault.import.wizard.path", + "Path to import", + default="", + domain="[('uuid', '=', uuid)]", + ) + + @api.onchange("crypted_content", "content") + def _onchange_content(self): + for rec in self: + if rec.crypted_content: + for entry in json.loads(rec.crypted_content or []): + rec._create_path(entry) + + def _create_path(self, entry, path=None): + self.ensure_one() + p = f"{path} / {entry['name']}" if path else entry["name"] + + if "name" in entry: + self.env["vault.import.wizard.path"].create({"uuid": self.uuid, "name": p}) + + for child in entry.get("childs", []): + self._create_path(child, p) + + def _import_field(self, entry, model, data): + if not data: + return + + # Only copy specific fields + vals = {f: data[f] for f in ["name", "iv", "value"]} + + # Update already existing records + domain = [("entry_id", "=", entry.id), ("name", "=", data["name"])] + rec = model.search(domain) + if rec: + rec.write(vals) + else: + rec.create({"entry_id": entry.id, **vals}) + + def _import_entry(self, entry, parent=None, path=None): + p = f"{path} / {entry['name']}" if path else entry["name"] + result = self.env["vault.entry"] + if p.startswith(self.path.name or ""): + if not parent: + parent = self.env["vault.entry"] + + # Update existing records if already imported + rec = self.env["vault.entry"] + if entry.get("uuid"): + domain = [ + ("uuid", "=", entry["uuid"]), + ("vault_id", "=", self.vault_id.id), + ] + rec = rec.search(domain, limit=1) + + # If record not found create a new one + vals = {f: entry.get(f) for f in ["name", "note", "url", "uuid"]} + if not rec: + rec = rec.create( + {"vault_id": self.vault_id.id, "parent_id": parent.id, **vals} + ) + else: + rec.write({"parent_id": parent.id, **vals}) + + # Create/update the entry fields + for field in entry.get("fields", []): + self._import_field(rec, self.env["vault.field"], field) + + # Create/update the entry files + for file in entry.get("files", []): + self._import_field(rec, self.env["vault.file"], file) + + result |= rec + + else: + rec = None + + # Create the sub-entries + for child in entry.get("childs", []): + result |= self._import_entry(child, rec, p) + + return result + + def action_import(self): + self.ensure_one() + + try: + data = json.loads(self.crypted_content) + entries = self.env["vault.entry"] + for entry in data: + entries |= self.with_context(skip_log=True)._import_entry( + entry, self.parent_id + ) + + self.vault_id.log_entry(f"Imported entries from file {self.name}") + except Exception as e: + raise UserError(_("Invalid file to import from")) from e diff --git a/vault/wizards/vault_import_wizard.xml b/vault/wizards/vault_import_wizard.xml new file mode 100644 index 0000000000..642a6fc99c --- /dev/null +++ b/vault/wizards/vault_import_wizard.xml @@ -0,0 +1,52 @@ + + + + vault.import.wizard + +
+ + + + + + + +
The files must end on one of the supported file type:
+
    +
  • Custom JSON format .json
  • +
  • Keepass Database .kdbx
  • +
+ + + + + + +
+
+
+
+
+
+
diff --git a/vault/wizards/vault_send_wizard.py b/vault/wizards/vault_send_wizard.py new file mode 100644 index 0000000000..bf332fd448 --- /dev/null +++ b/vault/wizards/vault_send_wizard.py @@ -0,0 +1,56 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class VaultSendWizard(models.TransientModel): + _name = "vault.send.wizard" + _description = "Wizard to send another user a secret" + + user_id = fields.Many2one( + "res.users", + "User", + required=True, + domain=[("keys", "!=", False), ("inbox_enabled", "=", True)], + ) + name = fields.Char(required=True) + public = fields.Char(related="user_id.active_key.public") + iv = fields.Char(required=True) + key_user = fields.Char(required=True) + key = fields.Char(required=True) + secret = fields.Char() + secret_file = fields.Char() + filename = fields.Char() + + _sql_constraints = [ + ( + "value_check", + "CHECK(secret IS NOT NULL OR secret_file IS NOT NULL)", + "No value found", + ), + ] + + def action_send(self): + if not self.secret and not self.secret_file: + raise ValidationError(_("Neither a secret nor file was given")) + + self.ensure_one() + self.env["vault.inbox"].sudo().create( + { + "name": self.name, + "accesses": 0, + "secret": self.secret, + "secret_file": self.secret_file, + "iv": self.iv, + "key": self.key_user, + "user_id": self.user_id.id, + "filename": self.filename, + "log_ids": [(0, 0, {"name": _("Created by %s") % self.user_id.name})], + } + ) diff --git a/vault/wizards/vault_send_wizard.xml b/vault/wizards/vault_send_wizard.xml new file mode 100644 index 0000000000..0f4e359798 --- /dev/null +++ b/vault/wizards/vault_send_wizard.xml @@ -0,0 +1,37 @@ + + + + vault.send.wizard + +
+ +
+ You can only send the secret to the user who has generated a key-pair. + If an user is not showing please ask him to generate these. +
+ + + + + + + + + +
+
+
+
+
+
+
diff --git a/vault/wizards/vault_store_wizard.py b/vault/wizards/vault_store_wizard.py new file mode 100644 index 0000000000..a6713d807f --- /dev/null +++ b/vault/wizards/vault_store_wizard.py @@ -0,0 +1,47 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultStoreWizard(models.TransientModel): + _name = "vault.store.wizard" + _description = "Wizard store a shared secret in a vault" + + vault_id = fields.Many2one("vault", "Vault", required=True) + entry_id = fields.Many2one( + "vault.entry", + "Entry", + domain="[('vault_id', '=', vault_id)]", + required=True, + ) + model = fields.Char(required=True) + master_key = fields.Char(compute="_compute_master_key", store=False) + name = fields.Char(required=True) + iv = fields.Char(required=True) + key = fields.Char(required=True) + secret = fields.Char(required=True) + secret_temporary = fields.Char(required=True) + + @api.depends("entry_id", "vault_id") + def _compute_master_key(self): + for rec in self: + rec.master_key = rec.vault_id.master_key + + def action_store(self): + self.ensure_one() + try: + self.env[self.model].create( + { + "entry_id": self.entry_id.id, + "name": self.name, + "iv": self.iv, + "value": self.secret, + } + ) + except Exception as e: + _logger.exception(e) diff --git a/vault/wizards/vault_store_wizard.xml b/vault/wizards/vault_store_wizard.xml new file mode 100644 index 0000000000..aa91c476df --- /dev/null +++ b/vault/wizards/vault_store_wizard.xml @@ -0,0 +1,38 @@ + + + + vault.store.wizard + +
+ + + + + + + + + + + + +
+
+
+
+
+
diff --git a/vault_share/README.rst b/vault_share/README.rst new file mode 100644 index 0000000000..76c77bd011 --- /dev/null +++ b/vault_share/README.rst @@ -0,0 +1,100 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============= +Vault - Share +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:de985f1f7b46fdf2880937e5d686d814a723b149913d09847320facdf9dc7a49 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github + :target: https://github.com/OCA/server-auth/tree/18.0/vault_share + :alt: OCA/server-auth +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-vault_share + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module implements possibilities to share specific secrets with +external users. This bases on the vault implementation and the generated +RSA key pair. + +Share +----- + +This allows an user to share a secret with external users. A share can +be generated from a vault entry or directly created by an user. The +secret is symmetrically encrypted by a key derived from a pin. To grant +access the user has to transmit the link and pin with the external. If +either the access counter reaches 0 or the share expires it will be +deleted automatically. Due to the usage of a numeric pin and the browser +side decryption a share is vulnerable to brute-force attacks and +shouldn't be used as a permanent storage for secrets. For long time uses +the user should create an account and a vault should be used. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +- Secure the download of the encrypted file behind a challenge/response + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* initOS GmbH + +Contributors +------------ + +- Florian Kantelberg + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/vault_share/__init__.py b/vault_share/__init__.py new file mode 100644 index 0000000000..05ae53c8cb --- /dev/null +++ b/vault_share/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import controllers, models diff --git a/vault_share/__manifest__.py b/vault_share/__manifest__.py new file mode 100644 index 0000000000..00c82bf82b --- /dev/null +++ b/vault_share/__manifest__.py @@ -0,0 +1,35 @@ +# © 2021-2024 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Vault - Share", + "summary": "Implementation of a mechanism to share secrets", + "license": "AGPL-3", + "version": "18.0.1.0.0", + "website": "https://github.com/OCA/server-auth", + "application": False, + "author": "initOS GmbH, Odoo Community Association (OCA)", + "category": "Vault", + "depends": ["vault"], + "data": [ + "data/ir_cron.xml", + "security/ir.model.access.csv", + "security/ir_rule.xml", + "views/menuitems.xml", + "views/res_config_settings_views.xml", + "views/templates.xml", + "views/vault_share_views.xml", + ], + "assets": { + "web.assets_backend": [ + "vault_share/static/src/common/**/*.js", + "vault_share/static/src/backend/**/*.js", + "vault_share/static/src/backend/**/*.scss", + "vault_share/static/src/backend/**/*.xml", + ], + "vault_share.assets_frontend": [ + "vault/static/src/common/*.js", + "vault_share/static/src/frontend/*.js", + ], + }, +} diff --git a/vault_share/controllers/__init__.py b/vault_share/controllers/__init__.py new file mode 100644 index 0000000000..aabfa83edd --- /dev/null +++ b/vault_share/controllers/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import main diff --git a/vault_share/controllers/main.py b/vault_share/controllers/main.py new file mode 100644 index 0000000000..52eb94d1ef --- /dev/null +++ b/vault_share/controllers/main.py @@ -0,0 +1,36 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, http +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class Controller(http.Controller): + @http.route("/vault/share/", type="http", auth="public") + def vault_share(self, token): + ctx = {"disable_footer": True, "token": token} + share = request.env["vault.share"].sudo() + secret = share.get(token, ip=request.httprequest.remote_addr) + if secret is None: + ctx["error"] = _("The secret expired") + return request.render("vault_share.share", ctx) + + if len(secret) != 1: + ctx["error"] = _("Invalid token") + return request.render("vault_share.share", ctx) + + ctx.update( + { + "encrypted": secret.secret, + "salt": secret.salt, + "iv": secret.iv, + "encrypted_file": secret.secret_file, + "filename": secret.filename, + "iterations": secret.iterations, + } + ) + return request.render("vault_share.share", ctx) diff --git a/vault_share/data/ir_cron.xml b/vault_share/data/ir_cron.xml new file mode 100644 index 0000000000..7881ce42a0 --- /dev/null +++ b/vault_share/data/ir_cron.xml @@ -0,0 +1,12 @@ + + + + Clean outgoing share + + code + model.clean() + 1 + minutes + + + diff --git a/vault_share/i18n/es.po b/vault_share/i18n/es.po new file mode 100644 index 0000000000..a73a93b1cb --- /dev/null +++ b/vault_share/i18n/es.po @@ -0,0 +1,296 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault_share +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-06-06 07:44+0000\n" +"PO-Revision-Date: 2023-10-30 21:37+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__accesses +msgid "Access counter" +msgstr "Contador de acceso" + +#. module: vault_share +#: model:ir.actions.server,name:vault_share.cron_share_clean_ir_actions_server +#: model:ir.cron,cron_name:vault_share.cron_share_clean +#: model:ir.cron,name:vault_share.cron_share_clean +msgid "Clean outgoing share" +msgstr "Limpiar recurso compartido externo" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de Configuración" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Days" +msgstr "Días" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Delay the deletion of shares" +msgstr "Retrasar el borrado de recursos compartidos" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_config_settings__vault_share_delay +msgid "Delayed Deletion" +msgstr "Retrasar borrado" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_res_config_settings__vault_share_delay +msgid "" +"Delays the deletion of a share. After the expiration date it continues to " +"stay inaccessible" +msgstr "" +"Retrasa la eliminación de un recurso compartido. Después de la fecha de " +"caducidad sigue siendo inaccesible" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__display_name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Enter the pin:" +msgstr "Ingresa el pin:" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__expiration +msgid "Expiration" +msgstr "Expiración" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__filename +msgid "Filename" +msgstr "Nombre del fichero" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__id +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__id +msgid "ID" +msgstr "ID" + +#. module: vault_share +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Token inválido" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iv +msgid "Iv" +msgstr "Iv" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share____last_update +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_uid +msgid "Last Updated by" +msgstr "Última modificación por" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__log_ids +msgid "Log" +msgstr "Registro" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__name +msgid "Name" +msgstr "Nombre" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model.constraint,message:vault_share.constraint_vault_share_value_check +#, python-format +msgid "No value found" +msgstr "No se ha encontrado ningún valor" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__pin +msgid "Pin" +msgstr "Pin" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__salt +msgid "Salt" +msgstr "Sal" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/backend/templates.xml:0 +#, python-format +msgid "Save in a vault" +msgstr "Guardar en un vault" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret +msgid "Secret" +msgstr "Secreto" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret_file +msgid "Secret File" +msgstr "Archivo de secretos" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__share_id +msgid "Share" +msgstr "Compartir" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__share_link +msgid "Share URL" +msgstr "Compartir url" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/legacy/vault_fields.js:0 +#, python-format +msgid "Share the secret" +msgstr "Compartir el secreto" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/backend/templates.xml:0 +#, python-format +msgid "Share the secret with an external user" +msgstr "Compartir el secreto con usuario externo" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared file:" +msgstr "Archivo compartido:" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared secret:" +msgstr "Secreto compartido:" + +#. module: vault_share +#: model:ir.actions.act_window,name:vault_share.action_vault_share +#: model:ir.ui.menu,name:vault_share.menu_vault_share +msgid "Shares" +msgstr "Recursos compartidos" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__expiration +msgid "Specifies how long a share can be accessed until deletion." +msgstr "" +"Especifica el tiempo que se puede acceder a un recurso compartido hasta su " +"eliminación." + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__accesses +msgid "Specifies how often a share can be accessed before deletion." +msgstr "" +"Especifica la frecuencia con la que se puede acceder a un recurso compartido " +"antes de eliminarlo." + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__pin +msgid "The pin needed to decrypt the share." +msgstr "El pin necesario para descifrar el recurso compartido." + +#. module: vault_share +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "The secret expired" +msgstr "El secreto ha expirado" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was accessed by %(name)s via %(ip)s" +msgstr "%(name)s ha accedido a la acción a través de %(ip)s" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was created by %(name)s" +msgstr "La acción fue creada por %(name)s" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__token +msgid "Token" +msgstr "Token" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__user_id +msgid "User" +msgstr "Usuario" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__share_link +msgid "Using this link and pin people can access the secret." +msgstr "Utilizando este enlace y el pin la gente puede acceder al secreto." + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_company__vault_share_delay +msgid "Vault Share Delay" +msgstr "Retraso de la Acción de la Bóveda" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share_log.py:0 +#: model:ir.model,name:vault_share.model_vault_share_log +#, python-format +msgid "Vault share log" +msgstr "Registro de compartición de la bóveda" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model,name:vault_share.model_vault_share +#, python-format +msgid "Vault share outgoing secrets" +msgstr "La bóveda comparte secretos de salida" + +#, python-format +#~ msgid "The share was accessed by %s via %s" +#~ msgstr "El recurso compartido fue accedido por %s a través de %s" + +#, python-format +#~ msgid "The share was created by %s" +#~ msgstr "El recurso compartido fue creado por %s" diff --git a/vault_share/i18n/it.po b/vault_share/i18n/it.po new file mode 100644 index 0000000000..f6b1a8c662 --- /dev/null +++ b/vault_share/i18n/it.po @@ -0,0 +1,308 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault_share +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-03-08 15:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__accesses +msgid "Access counter" +msgstr "Contatore accessi" + +#. module: vault_share +#: model:ir.actions.server,name:vault_share.cron_share_clean_ir_actions_server +#: model:ir.cron,cron_name:vault_share.cron_share_clean +msgid "Clean outgoing share" +msgstr "Pulizia condivisione in uscita" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Days" +msgstr "Giorni" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Delay the deletion of shares" +msgstr "Ritarda la cancellazione delle condivisioni" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_config_settings__vault_share_delay +msgid "Delayed Deletion" +msgstr "Cancellazioni ritardate" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_res_config_settings__vault_share_delay +msgid "" +"Delays the deletion of a share. After the expiration date it continues to " +"stay inaccessible" +msgstr "" +"Ritarda la cancellazione di una condivisione. Dopo la scadenza continua ad " +"essere inaccessibile" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__display_name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Enter the pin:" +msgstr "Inserire il PIN:" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__expiration +msgid "Expiration" +msgstr "Scadenza" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__filename +msgid "Filename" +msgstr "Nome file" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__id +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__id +msgid "ID" +msgstr "ID" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Token non valido" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iterations +msgid "Iterations" +msgstr "Iterazioni" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iv +msgid "Iv" +msgstr "Iv" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share____last_update +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__log_ids +msgid "Log" +msgstr "Log" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__name +msgid "Name" +msgstr "Nome" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model.constraint,message:vault_share.constraint_vault_share_value_check +#, python-format +msgid "No value found" +msgstr "Nessun valore trovato" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__pin +msgid "Pin" +msgstr "PIN" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__salt +msgid "Salt" +msgstr "Salt" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret +msgid "Secret" +msgstr "Segreto" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret_file +msgid "Secret File" +msgstr "File segreto" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__share_id +msgid "Share" +msgstr "Condividi" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__share_link +msgid "Share URL" +msgstr "URL condivisione" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_field.esm.js:0 +#, python-format +msgid "Share the secret" +msgstr "Condividi il segreto" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/templates.xml:0 +#: code:addons/vault_share/static/src/backend/fields/templates.xml:0 +#, python-format +msgid "Share the secret with an external user" +msgstr "Condividi il segreto con un utente esterno" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared file:" +msgstr "File condiviso:" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared secret:" +msgstr "Segreto condiviso:" + +#. module: vault_share +#: model:ir.actions.act_window,name:vault_share.action_vault_share +#: model:ir.ui.menu,name:vault_share.menu_vault_share +msgid "Shares" +msgstr "Condivisioni" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__expiration +msgid "Specifies how long a share can be accessed until deletion." +msgstr "" +"Indica per quanto tempo si può accedere ad una condivisione prima della " +"cancellazione." + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__accesses +msgid "Specifies how often a share can be accessed before deletion." +msgstr "" +"Indica quante volte si può accedere ad una condivisione prima della " +"cancellazione." + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__pin +msgid "The pin needed to decrypt the share." +msgstr "Il PIN richiesto per decriptare la condivisione." + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "The secret expired" +msgstr "Il segreto è scaduto" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was accessed by %(name)s via %(ip)s" +msgstr "La codivisione è stata utilizzata da %(name)s attraverso %(ip)s" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was created by %(name)s" +msgstr "La condivisione è stata creata da %(name)s" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__token +msgid "Token" +msgstr "Token" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__user_id +msgid "User" +msgstr "Utente" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__share_link +msgid "Using this link and pin people can access the secret." +msgstr "" +"Utilizzando questo collegamento e PIN le persone possono accedere al segreto." + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_pin_field.esm.js:0 +#, python-format +msgid "Vault Pin Field" +msgstr "Campo PIN deposito di sicurezza" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_company__vault_share_delay +msgid "Vault Share Delay" +msgstr "Ritardo condivisione deposito di sicurezza" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_share_field.esm.js:0 +#: code:addons/vault_share/static/src/backend/fields/vault_share_file.esm.js:0 +#, python-format +msgid "Vault Share Field" +msgstr "Campo condivisione deposito di sicurezza" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share_log.py:0 +#: model:ir.model,name:vault_share.model_vault_share_log +#, python-format +msgid "Vault share log" +msgstr "Log condivisione deposito di sicurezza" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model,name:vault_share.model_vault_share +#, python-format +msgid "Vault share outgoing secrets" +msgstr "Segreti uscita condivisione deposito di sicurezza" diff --git a/vault_share/i18n/nl.po b/vault_share/i18n/nl.po new file mode 100644 index 0000000000..5ea11a1ac3 --- /dev/null +++ b/vault_share/i18n/nl.po @@ -0,0 +1,281 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault_share +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-05-05 13:33+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__accesses +msgid "Access counter" +msgstr "Toegangsteller" + +#. module: vault_share +#: model:ir.actions.server,name:vault_share.cron_share_clean_ir_actions_server +#: model:ir.cron,cron_name:vault_share.cron_share_clean +#: model:ir.cron,name:vault_share.cron_share_clean +msgid "Clean outgoing share" +msgstr "" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_company +msgid "Companies" +msgstr "Bedrijven" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_config_settings +msgid "Config Settings" +msgstr "Configuratie-instellingen" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Days" +msgstr "Dagen" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Delay the deletion of shares" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_config_settings__vault_share_delay +msgid "Delayed Deletion" +msgstr "Uitgestelde verwijdering" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_res_config_settings__vault_share_delay +msgid "" +"Delays the deletion of a share. After the expiration date it continues to " +"stay inaccessible" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__display_name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__display_name +msgid "Display Name" +msgstr "Schermnaam" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Enter the pin:" +msgstr "Voer de pincode in:" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__expiration +msgid "Expiration" +msgstr "Vervaldatum" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__filename +msgid "Filename" +msgstr "Bestandsnaam" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__id +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__id +msgid "ID" +msgstr "ID" + +#. module: vault_share +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "Invalid token" +msgstr "Ongeldige token" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iv +msgid "Iv" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share____last_update +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log____last_update +msgid "Last Modified on" +msgstr "Laatst gewijzigd op" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_uid +msgid "Last Updated by" +msgstr "Laatst bijgewerkt door" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_date +msgid "Last Updated on" +msgstr "Laatst bijgewerkt op" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__log_ids +msgid "Log" +msgstr "Log" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__name +msgid "Name" +msgstr "Naam" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model.constraint,message:vault_share.constraint_vault_share_value_check +#, python-format +msgid "No value found" +msgstr "Geen waarde gevonden" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__pin +msgid "Pin" +msgstr "Pin" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__salt +msgid "Salt" +msgstr "" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/backend/templates.xml:0 +#, python-format +msgid "Save in a vault" +msgstr "Opslaan in een kluis" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret +msgid "Secret" +msgstr "Geheim" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret_file +msgid "Secret File" +msgstr "Secret File" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__share_id +msgid "Share" +msgstr "Delen" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__share_link +msgid "Share URL" +msgstr "Share URL" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/legacy/vault_fields.js:0 +#, python-format +msgid "Share the secret" +msgstr "Deel het geheim" + +#. module: vault_share +#. openerp-web +#: code:addons/vault_share/static/src/backend/templates.xml:0 +#, python-format +msgid "Share the secret with an external user" +msgstr "Het geheim delen met een externe gebruiker" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared file:" +msgstr "Gedeeld bestand:" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared secret:" +msgstr "Gedeeld geheim:" + +#. module: vault_share +#: model:ir.actions.act_window,name:vault_share.action_vault_share +#: model:ir.ui.menu,name:vault_share.menu_vault_share +msgid "Shares" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__expiration +msgid "Specifies how long a share can be accessed until deletion." +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__accesses +msgid "Specifies how often a share can be accessed before deletion." +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__pin +msgid "The pin needed to decrypt the share." +msgstr "" + +#. module: vault_share +#: code:addons/vault_share/controllers/main.py:0 +#, python-format +msgid "The secret expired" +msgstr "Het geheim is verlopen" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was accessed by %(name)s via %(ip)s" +msgstr "Het record werd geopend door %(name)s via %(ip)s" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#, python-format +msgid "The share was created by %(name)s" +msgstr "Het record is gemaakt door %(name)s" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__token +msgid "Token" +msgstr "Token" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__user_id +msgid "User" +msgstr "Gebruiker" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__share_link +msgid "Using this link and pin people can access the secret." +msgstr "Met deze link en pin kunnen mensen toegang krijgen tot het geheim." + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_company__vault_share_delay +msgid "Vault Share Delay" +msgstr "" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share_log.py:0 +#: model:ir.model,name:vault_share.model_vault_share_log +#, python-format +msgid "Vault share log" +msgstr "" + +#. module: vault_share +#: code:addons/vault_share/models/vault_share.py:0 +#: model:ir.model,name:vault_share.model_vault_share +#, python-format +msgid "Vault share outgoing secrets" +msgstr "" diff --git a/vault_share/i18n/vault_share.pot b/vault_share/i18n/vault_share.pot new file mode 100644 index 0000000000..f8c357ca0f --- /dev/null +++ b/vault_share/i18n/vault_share.pot @@ -0,0 +1,273 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault_share +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \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: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__accesses +msgid "Access counter" +msgstr "" + +#. module: vault_share +#: model:ir.actions.server,name:vault_share.cron_share_clean_ir_actions_server +msgid "Clean outgoing share" +msgstr "" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_company +msgid "Companies" +msgstr "" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_uid +msgid "Created by" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__create_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__create_date +msgid "Created on" +msgstr "" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Days" +msgstr "" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.res_config_settings_view_form +msgid "Delay the deletion of shares" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_config_settings__vault_share_delay +msgid "Delayed Deletion" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_res_config_settings__vault_share_delay +msgid "" +"Delays the deletion of a share. After the expiration date it continues to " +"stay inaccessible" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__display_name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__display_name +msgid "Display Name" +msgstr "" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Enter the pin:" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__expiration +msgid "Expiration" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__filename +msgid "Filename" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__id +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__id +msgid "ID" +msgstr "" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/controllers/main.py:0 +msgid "Invalid token" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iterations +msgid "Iterations" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__iv +msgid "Iv" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_uid +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__write_date +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__write_date +msgid "Last Updated on" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__log_ids +msgid "Log" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__name +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__name +msgid "Name" +msgstr "" + +#. module: vault_share +#: model:ir.model.constraint,message:vault_share.constraint_vault_share_value_check +msgid "No value found" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__pin +msgid "Pin" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__salt +msgid "Salt" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret +msgid "Secret" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__secret_file +msgid "Secret File" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share_log__share_id +msgid "Share" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__share_link +msgid "Share URL" +msgstr "" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_field.esm.js:0 +msgid "Share the secret" +msgstr "" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/templates.xml:0 +msgid "Share the secret with an external user" +msgstr "" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared file:" +msgstr "" + +#. module: vault_share +#: model_terms:ir.ui.view,arch_db:vault_share.share +msgid "Shared secret:" +msgstr "" + +#. module: vault_share +#: model:ir.actions.act_window,name:vault_share.action_vault_share +#: model:ir.ui.menu,name:vault_share.menu_vault_share +msgid "Shares" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__expiration +msgid "Specifies how long a share can be accessed until deletion." +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__accesses +msgid "Specifies how often a share can be accessed before deletion." +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__pin +msgid "The pin needed to decrypt the share." +msgstr "" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/controllers/main.py:0 +msgid "The secret expired" +msgstr "" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +msgid "The share was accessed by %(name)s via %(ip)s" +msgstr "" + +#. module: vault_share +#. odoo-python +#: code:addons/vault_share/models/vault_share.py:0 +msgid "The share was created by %(name)s" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__token +msgid "Token" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_vault_share__user_id +msgid "User" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,help:vault_share.field_vault_share__share_link +msgid "Using this link and pin people can access the secret." +msgstr "" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_pin_field.esm.js:0 +msgid "Vault Pin Field" +msgstr "" + +#. module: vault_share +#: model:ir.model.fields,field_description:vault_share.field_res_company__vault_share_delay +msgid "Vault Share Delay" +msgstr "" + +#. module: vault_share +#. odoo-javascript +#: code:addons/vault_share/static/src/backend/fields/vault_share_field.esm.js:0 +#: code:addons/vault_share/static/src/backend/fields/vault_share_file.esm.js:0 +msgid "Vault Share Field" +msgstr "" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_vault_share_log +msgid "Vault share log" +msgstr "" + +#. module: vault_share +#: model:ir.model,name:vault_share.model_vault_share +msgid "Vault share outgoing secrets" +msgstr "" diff --git a/vault_share/models/__init__.py b/vault_share/models/__init__.py new file mode 100644 index 0000000000..e6b947e32a --- /dev/null +++ b/vault_share/models/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import res_company, res_config_settings, vault_share, vault_share_log diff --git a/vault_share/models/res_company.py b/vault_share/models/res_company.py new file mode 100644 index 0000000000..8eac8cd823 --- /dev/null +++ b/vault_share/models/res_company.py @@ -0,0 +1,10 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class RecCompany(models.Model): + _inherit = "res.company" + + vault_share_delay = fields.Integer(default=0) diff --git a/vault_share/models/res_config_settings.py b/vault_share/models/res_config_settings.py new file mode 100644 index 0000000000..4a69c1c18b --- /dev/null +++ b/vault_share/models/res_config_settings.py @@ -0,0 +1,24 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + vault_share_delay = fields.Integer( + string="Delayed Deletion", + related="company_id.vault_share_delay", + readonly=False, + help="Delays the deletion of a share. After the expiration date it continues " + "to stay inaccessible", + ) + + @api.onchange("vault_share_delay") + def _onchange_vault_share_delay(self): + self.vault_share_delay = max(0, self.vault_share_delay) diff --git a/vault_share/models/vault_share.py b/vault_share/models/vault_share.py new file mode 100644 index 0000000000..3be86b6d36 --- /dev/null +++ b/vault_share/models/vault_share.py @@ -0,0 +1,86 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +from uuid import uuid4 + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultShare(models.Model): + _name = "vault.share" + _description = "Vault share outgoing secrets" + + user_id = fields.Many2one("res.users", default=lambda self: self.env.uid) + name = fields.Char(required=True) + share_link = fields.Char( + "Share URL", + compute="_compute_url", + store=False, + help="Using this link and pin people can access the secret.", + ) + token = fields.Char(readonly=True, required=True, default=lambda self: uuid4()) + secret = fields.Char() + secret_file = fields.Char() + filename = fields.Char() + salt = fields.Char(required=True) + iterations = fields.Integer() + iv = fields.Char(required=True) + pin = fields.Char(required=True, help="The pin needed to decrypt the share.") + accesses = fields.Integer( + "Access counter", + default=5, + help="Specifies how often a share can be accessed before deletion.", + ) + expiration = fields.Datetime( + default=lambda self: datetime.now() + timedelta(days=7), + help="Specifies how long a share can be accessed until deletion.", + ) + log_ids = fields.One2many("vault.share.log", "share_id", "Log", readonly=True) + + _sql_constraints = [ + ( + "value_check", + "CHECK(secret IS NOT NULL OR secret_file IS NOT NULL)", + "No value found", + ), + ] + + @api.depends("token") + def _compute_url(self): + base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url") + for rec in self: + rec.share_link = f"{base_url}/vault/share/{rec.token}" + + @api.model + def get(self, token, ip=None): + rec = self.search([("token", "=", token)], limit=1) + if not rec: + return rec + + if datetime.now() < rec.expiration and rec.accesses > 0: + rec.accesses -= 1 + log = self.env._("The share was accessed by %(name)s via %(ip)s") + rec.log_ids = [ + (0, 0, {"name": log % {"name": self.env.user.name, "ip": ip or "n/a"}}) + ] + return rec + + return None + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + log = self.env._("The share was created by %(name)s") + for rec in res: + rec.log_ids = [(0, 0, {"name": log % {"name": self.env.user.name}})] + return res + + @api.model + def clean(self): + now = datetime.now() + offset = timedelta(days=self.env.company.vault_share_delay) + self.search([("expiration", "<=", now + offset)]).unlink() diff --git a/vault_share/models/vault_share_log.py b/vault_share/models/vault_share_log.py new file mode 100644 index 0000000000..dc4126bca6 --- /dev/null +++ b/vault_share/models/vault_share_log.py @@ -0,0 +1,22 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class VaultShareLog(models.Model): + _name = "vault.share.log" + _description = "Vault share log" + _order = "create_date DESC" + + share_id = fields.Many2one( + "vault.share", + ondelete="cascade", + readonly=True, + required=True, + ) + name = fields.Char(readonly=True) diff --git a/vault_share/pyproject.toml b/vault_share/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/vault_share/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/vault_share/readme/CONTRIBUTORS.md b/vault_share/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..5710e10104 --- /dev/null +++ b/vault_share/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Florian Kantelberg \ diff --git a/vault_share/readme/DESCRIPTION.md b/vault_share/readme/DESCRIPTION.md new file mode 100644 index 0000000000..5559e57d9e --- /dev/null +++ b/vault_share/readme/DESCRIPTION.md @@ -0,0 +1,15 @@ +This module implements possibilities to share specific secrets with +external users. This bases on the vault implementation and the generated +RSA key pair. + +## Share + +This allows an user to share a secret with external users. A share can +be generated from a vault entry or directly created by an user. The +secret is symmetrically encrypted by a key derived from a pin. To grant +access the user has to transmit the link and pin with the external. If +either the access counter reaches 0 or the share expires it will be +deleted automatically. Due to the usage of a numeric pin and the browser +side decryption a share is vulnerable to brute-force attacks and +shouldn't be used as a permanent storage for secrets. For long time uses +the user should create an account and a vault should be used. diff --git a/vault_share/readme/ROADMAP.md b/vault_share/readme/ROADMAP.md new file mode 100644 index 0000000000..8cf6f997c9 --- /dev/null +++ b/vault_share/readme/ROADMAP.md @@ -0,0 +1 @@ +- Secure the download of the encrypted file behind a challenge/response diff --git a/vault_share/security/ir.model.access.csv b/vault_share/security/ir.model.access.csv new file mode 100644 index 0000000000..54569a817c --- /dev/null +++ b/vault_share/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_vault_share,access_vault_share,model_vault_share,base.group_user,1,1,1,1 +access_vault_share_log,access_vault_share_log,model_vault_share_log,base.group_user,1,1,1,1 diff --git a/vault_share/security/ir_rule.xml b/vault_share/security/ir_rule.xml new file mode 100644 index 0000000000..e9a1dc2576 --- /dev/null +++ b/vault_share/security/ir_rule.xml @@ -0,0 +1,22 @@ + + + + vault.share.access.owner + + [('user_id', '=', user.id)] + + + + + + + + vault.share.log.access.owner + + [('share_id.user_id', '=', user.id)] + + + + + + diff --git a/vault_share/static/description/icon.png b/vault_share/static/description/icon.png new file mode 100644 index 0000000000..d79aa6f575 Binary files /dev/null and b/vault_share/static/description/icon.png differ diff --git a/vault_share/static/description/index.html b/vault_share/static/description/index.html new file mode 100644 index 0000000000..7546e8b0dc --- /dev/null +++ b/vault_share/static/description/index.html @@ -0,0 +1,445 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Vault - Share

+ +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

This module implements possibilities to share specific secrets with +external users. This bases on the vault implementation and the generated +RSA key pair.

+
+

Share

+

This allows an user to share a secret with external users. A share can +be generated from a vault entry or directly created by an user. The +secret is symmetrically encrypted by a key derived from a pin. To grant +access the user has to transmit the link and pin with the external. If +either the access counter reaches 0 or the share expires it will be +deleted automatically. Due to the usage of a numeric pin and the browser +side decryption a share is vulnerable to brute-force attacks and +shouldn’t be used as a permanent storage for secrets. For long time uses +the user should create an account and a vault should be used.

+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Secure the download of the encrypted file behind a challenge/response
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+ +
+
+

Authors

+
    +
  • initOS GmbH
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/vault_share/static/src/backend/fields/templates.xml b/vault_share/static/src/backend/fields/templates.xml new file mode 100644 index 0000000000..de69f7863d --- /dev/null +++ b/vault_share/static/src/backend/fields/templates.xml @@ -0,0 +1,44 @@ + + + + + !isNew + + + + + + isNew + + + + +
+ ******* +
+
+ + +
+
+ + + +