diff --git a/vault/README.rst b/vault/README.rst new file mode 100644 index 0000000000..42c72397e3 --- /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:4c1e235277a22d56ad27351359792d02ea259f8824ebcd1fcfdbc9183e179c28 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/20.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-20-0/server-auth-20-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=20.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..963af27ad0 --- /dev/null +++ b/vault/__manifest__.py @@ -0,0 +1,49 @@ +# © 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": "20.0.1.0.0", + "website": "https://github.com/OCA/server-auth", + "application": True, + "author": "initOS GmbH, Odoo Community Association (OCA)", + "category": "Vault", + "depends": ["base_setup", "web"], + "data": [ + "security/vault_security.xml", + "security/ir.access.csv", + "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.assets_unit_tests": [ + "vault/static/tests/**/*.test.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..d798059130 --- /dev/null +++ b/vault/controllers/main.py @@ -0,0 +1,177 @@ +# © 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__) + +# Maximum size of the encrypted data which can be stored using the public inbox +DEFAULT_INBOX_MAX_SIZE = 10 * 1024 * 1024 + + +class Controller(http.Controller): + @http.route("/vault/inbox/", type="http", auth="public", website=True) + 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._is_writable(): + 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"] = request.env._("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"] = request.env._("Please specify a name") + return request.render("vault.inbox", ctx) + + if not secret and not secret_file: + ctx["error"] = request.env._("No secret found") + return request.render("vault.inbox", ctx) + + if secret_file and not filename: + ctx["error"] = request.env._("Missing filename") + return request.render("vault.inbox", ctx) + + if not iv or not key: + ctx["error"] = request.env._("Something went wrong with the encryption") + return request.render("vault.inbox", ctx) + + max_size = ( + request.env["ir.config_parameter"] + .sudo() + .get_int("vault.inbox_max_size", DEFAULT_INBOX_MAX_SIZE) + ) + if len(secret or "") + len(secret_file or "") > max_size: + ctx["error"] = request.env._("The secret is too big") + return request.render("vault.inbox", ctx) + + try: + stored = inbox.store_in_inbox( + name, + secret, + secret_file, + iv, + key, + user, + filename, + ip=request.httprequest.remote_addr, + ) + except Exception: + _logger.exception("Error storing the secret in the inbox") + ctx["error"] = request.env._( + "An error occured. Please contact the user or administrator" + ) + return request.render("vault.inbox", ctx) + + if not stored: + ctx["error"] = request.env._("The inbox can't be written anymore") + return request.render("vault.inbox", ctx) + + ctx["message"] = request.env._("Successfully stored") + return request.render("vault.inbox", ctx) + + @http.route("/vault/public", type="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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="jsonrpc") + 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: + if not isinstance(changes, dict): + raise AccessDenied() + + model, record_id = changes.get("model"), changes.get("id") + if model not in ("vault.field", "vault.file", "vault.right"): + raise AccessDenied() + if not isinstance(record_id, int): + raise AccessDenied() + + record = vault.env[model].browse(record_id).exists() + if len(record) != 1 or 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..362aa6c144 --- /dev/null +++ b/vault/i18n/es.po @@ -0,0 +1,1560 @@ +# 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: 2026-10-29 10:55+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 +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s entrada %(name)s por %(user)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%s (copy)" +msgstr "%s (copia)" + +#. module: vault +#: model:ir.module.module,description:base.module_vault +msgid "" +".. image:: https://odoo-community.org/readme-banner-image\n" +" :target: https://odoo-community.org/get-involved?utm_source=readme\n" +" :alt: Odoo Community Association\n" +"\n" +"=====\n" +"Vault\n" +"=====\n" +"\n" +".. \n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! This file is generated by oca-gen-addon-readme !!\n" +" !! changes will be overwritten. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! source digest: " +"sha256:4c1e235277a22d56ad27351359792d02ea259f8824ebcd1fcfdbc9183e179c28\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +"\n" +".. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png\n" +" :target: https://odoo-community.org/page/development-status\n" +" :alt: Beta\n" +".. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html\n" +" :alt: License: AGPL-3\n" +".. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-" +"lightgray.png?logo=github\n" +" :target: https://github.com/OCA/server-auth/tree/20.0/vault\n" +" :alt: OCA/server-auth\n" +".. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-" +"F47D42.png\n" +" :target: https://translation.odoo-community.org/projects/server-" +"auth-20-0/server-auth-20-0-vault\n" +" :alt: Translate me on Weblate\n" +".. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B." +"png\n" +" :target: https://runboat.odoo-community.org/builds?repo=OCA/server-" +"auth&target_branch=20.0\n" +" :alt: Try me on Runboat\n" +"\n" +"|badge1| |badge2| |badge3| |badge4| |badge5|\n" +"\n" +"This module implements a vault for secrets and files using\n" +"end-to-end-encryption. The encryption and decryption happens in the\n" +"browser using a vault specific shared master key. The master keys are\n" +"encrypted using asymmetrically. For this the user has to enter a second\n" +"password on the first login or if he needs to access data in a vault.\n" +"The asymmetric keys are stored for a certain time in the browser\n" +"storage.\n" +"\n" +"The server can never access the secrets with the information available.\n" +"Only people registered in the vault can decrypt or encrypt values in a\n" +"vault. The meta data isn't encrypted to be able to search/filter for\n" +"entries more easily.\n" +"\n" +"This modules requires a secure context for the browser to work properly\n" +"and therefore HTTPS support is required.\n" +"\n" +"The `vault-recovery `__\n" +"project focuses on disaster recovery in case of an incident to recover\n" +"secrets from old database backups or old exports.\n" +"\n" +"**Table of contents**\n" +"\n" +".. contents::\n" +" :local:\n" +"\n" +"Known issues / Roadmap\n" +"======================\n" +"\n" +"- Field and file history for restoration\n" +"- Import improvement\n" +"\n" +"..\n" +"\n" +" - Support challenge-response/FIDO2\n" +" - Support for argon2 and kdbx v4\n" +"\n" +"- When changing an entry from one vault to another existing vault, the\n" +" values added on this entry cannot be accessed, so the field vault is\n" +" going to be readonly when it is defined.\n" +"\n" +" If you want to move entries between vaults you can use the export ->\n" +" import option.\n" +"\n" +"- HTTPS or localhost (secure browser context) is required for the client\n" +" side encryption\n" +"\n" +"Bug Tracker\n" +"===========\n" +"\n" +"Bugs are tracked on `GitHub Issues `_.\n" +"In case of trouble, please check there if your issue has already been " +"reported.\n" +"If you spotted it first, help us to smash it by providing a detailed and " +"welcomed\n" +"`feedback `_.\n" +"\n" +"Do not contact contributors directly about support or help with technical " +"issues.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Authors\n" +"-------\n" +"\n" +"* initOS GmbH\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"- Florian Kantelberg \n" +"- `Tecnativa `__:\n" +"\n" +" - Carlos Roca\n" +"\n" +"Maintainers\n" +"-----------\n" +"\n" +"This module is maintained by the OCA.\n" +"\n" +".. image:: https://odoo-community.org/logo.png\n" +" :alt: Odoo Community Association\n" +" :target: https://odoo-community.org\n" +"\n" +"OCA, or the Odoo Community Association, is a nonprofit organization whose\n" +"mission is to support the collaborative development of Odoo features and\n" +"promote its widespread use.\n" +"\n" +"This module is part of the `OCA/server-auth `_ project on GitHub.\n" +"\n" +"You are welcome to contribute. To learn how please visit https://odoo-" +"community.org/page/Contribute.\n" +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 "" +"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 +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract_field +msgid "Abstract model to implement basic fields for encryption" +msgstr "Modelo abstracto para implementar los campos básicos de encriptación" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract +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 +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/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 "Cancelar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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/controller.esm.js:0 +msgid "Confirm" +msgstr "Confirmar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +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/wizards/vault_send_wizard.py:0 +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 +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Created via %(ip)s" +msgstr "Creado desde %(ip)s" + +#. 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 +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Discard" +msgstr "Descartar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:vault.field_res_users__display_name +#: 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_abstract__display_name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +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/backend/templates.xml:0 +msgid "Enter" +msgstr "Ingrese a" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +#: model:ir.model,name:vault.model_vault_entry +msgid "Entry inside a vault" +msgstr "Entrar al interior de un secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +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 +msgid "Export to file" +msgstr "Exportar a fichero" + +#. module: vault +#: model:ir.model,name:vault.model_vault_export_wizard +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 +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 +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 +msgid "Failed to import keys from database" +msgstr "Fallo en la importación de claves desde la base de datos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_field +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 +#: model:ir.model,name:vault.model_vault_file +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 +msgid "Generate" +msgstr "Generar" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Generate a new secret:" +msgstr "Generar nuevo secreto:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Hide" +msgstr "Ocultar" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__id +#: model:ir.model.fields,field_description:vault.field_res_users__id +#: 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_abstract__id +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +msgid "Import from file" +msgstr "Importar desde archivo" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard +msgid "Import wizard for vaults" +msgstr "Asistente de importación para secretos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard_path +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 +msgid "Information" +msgstr "Información" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +msgid "Invalid file to import from" +msgstr "Archivo inválido para importar" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +msgid "Invalid model to store the secret" +msgstr "Modelo no válido para guardar el secreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +msgid "Invalid parameter" +msgstr "Parámetro no válido" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +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 +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__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 +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 +#: model:ir.model,name:vault.model_vault_log +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 +msgid "Missing filename" +msgstr "Falta el nombre del fichero" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +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 +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 +msgid "No secret found" +msgstr "No se ha encontrado ningún secreto" + +#. 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 "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 +#: model:ir.module.module,summary:base.module_vault +msgid "Password vault integration in Odoo" +msgstr "Integración de un almacén de contraseñas en Odoo" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +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 +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 +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 +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 +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 +msgid "Save As..." +msgstr "Guardar como..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +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 +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Select at least one character set" +msgstr "Seleccione al menos un conjunto de caracteres" + +#. 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 +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 +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 +msgid "Show" +msgstr "Mostrar" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +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 +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 +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 +#: 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 "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 +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 +msgid "The following entries are broken:" +msgstr "Los siguientes registros están rotos:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The inbox can't be written anymore" +msgstr "Ya no se puede escribir en la bandeja de entrada" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "The passwords aren't matching" +msgstr "Las contraseñas no coinciden" + +#. 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 "" +"La operación solicitada no puede completarse debido a restricciones de " +"seguridad." + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The secret is too big" +msgstr "El secreto es demasiado grande" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +msgid "The tag must be unique!" +msgstr "¡La etiqueta debe ser única!" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +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 +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 +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Use password" +msgstr "Usar contraseña" + +#. 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 +#: model:ir.model,name:vault.model_res_users_key +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 +#: 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.module.module,shortdesc:base.module_vault +#: 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 "Secretos" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +msgid "Vault Field" +msgstr "Campo de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +msgid "Vault File" +msgstr "Fichero de secreto" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +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 +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-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +msgid "Vault export file" +msgstr "Fichero de exportación de secretos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox_log +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 +msgid "Vault is not supported" +msgstr "Secretos no está soportado" + +#. module: vault +#: model:ir.model,name:vault.model_vault_right +msgid "Vault rights" +msgstr "Permisos de secretos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox +msgid "Vault share incoming secrets" +msgstr "Compartir los secretos de entrada de almacén de secretos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_tag +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 +msgid "Warning" +msgstr "Advertencia" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "With Content" +msgstr "Con contenido" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Without Content" +msgstr "Sin contenido" + +#. module: vault +#: model:ir.model,name:vault.model_vault_store_wizard +msgid "Wizard store a shared secret in a vault" +msgstr "" +"Asistente de almacenado de secreto compartido en un almacén de secretos" + +#. module: vault +#: model:ir.model,name:vault.model_vault_send_wizard +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 +msgid "Written via %(ip)s" +msgstr "Escrito desde %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +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 +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't change the inbox of other users." +msgstr "No puede cambiar la bandeja de entrada de otros usuarios." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't invalidate the key of other users." +msgstr "No puede invalidar la clave de otros usuarios." + +#. 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 +msgid "a-z" +msgstr "a-z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "error" +msgstr "error" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "of entry" +msgstr "del registro" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "warn" +msgstr "advertencia" diff --git a/vault/i18n/it.po b/vault/i18n/it.po new file mode 100644 index 0000000000..944105446e --- /dev/null +++ b/vault/i18n/it.po @@ -0,0 +1,1561 @@ +# 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" +"POT-Creation-Date: 2026-10-29 10:55+0000\n" +"PO-Revision-Date: 2026-09-16 14:46+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 5.15.2\n" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s inserisce %(name)s da %(user)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%s (copy)" +msgstr "%s (copia)" + +#. module: vault +#: model:ir.module.module,description:base.module_vault +msgid "" +".. image:: https://odoo-community.org/readme-banner-image\n" +" :target: https://odoo-community.org/get-involved?utm_source=readme\n" +" :alt: Odoo Community Association\n" +"\n" +"=====\n" +"Vault\n" +"=====\n" +"\n" +".. \n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! This file is generated by oca-gen-addon-readme !!\n" +" !! changes will be overwritten. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! source digest: " +"sha256:4c1e235277a22d56ad27351359792d02ea259f8824ebcd1fcfdbc9183e179c28\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +"\n" +".. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png\n" +" :target: https://odoo-community.org/page/development-status\n" +" :alt: Beta\n" +".. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html\n" +" :alt: License: AGPL-3\n" +".. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-" +"lightgray.png?logo=github\n" +" :target: https://github.com/OCA/server-auth/tree/20.0/vault\n" +" :alt: OCA/server-auth\n" +".. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-" +"F47D42.png\n" +" :target: https://translation.odoo-community.org/projects/server-" +"auth-20-0/server-auth-20-0-vault\n" +" :alt: Translate me on Weblate\n" +".. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B." +"png\n" +" :target: https://runboat.odoo-community.org/builds?repo=OCA/server-" +"auth&target_branch=20.0\n" +" :alt: Try me on Runboat\n" +"\n" +"|badge1| |badge2| |badge3| |badge4| |badge5|\n" +"\n" +"This module implements a vault for secrets and files using\n" +"end-to-end-encryption. The encryption and decryption happens in the\n" +"browser using a vault specific shared master key. The master keys are\n" +"encrypted using asymmetrically. For this the user has to enter a second\n" +"password on the first login or if he needs to access data in a vault.\n" +"The asymmetric keys are stored for a certain time in the browser\n" +"storage.\n" +"\n" +"The server can never access the secrets with the information available.\n" +"Only people registered in the vault can decrypt or encrypt values in a\n" +"vault. The meta data isn't encrypted to be able to search/filter for\n" +"entries more easily.\n" +"\n" +"This modules requires a secure context for the browser to work properly\n" +"and therefore HTTPS support is required.\n" +"\n" +"The `vault-recovery `__\n" +"project focuses on disaster recovery in case of an incident to recover\n" +"secrets from old database backups or old exports.\n" +"\n" +"**Table of contents**\n" +"\n" +".. contents::\n" +" :local:\n" +"\n" +"Known issues / Roadmap\n" +"======================\n" +"\n" +"- Field and file history for restoration\n" +"- Import improvement\n" +"\n" +"..\n" +"\n" +" - Support challenge-response/FIDO2\n" +" - Support for argon2 and kdbx v4\n" +"\n" +"- When changing an entry from one vault to another existing vault, the\n" +" values added on this entry cannot be accessed, so the field vault is\n" +" going to be readonly when it is defined.\n" +"\n" +" If you want to move entries between vaults you can use the export ->\n" +" import option.\n" +"\n" +"- HTTPS or localhost (secure browser context) is required for the client\n" +" side encryption\n" +"\n" +"Bug Tracker\n" +"===========\n" +"\n" +"Bugs are tracked on `GitHub Issues `_.\n" +"In case of trouble, please check there if your issue has already been " +"reported.\n" +"If you spotted it first, help us to smash it by providing a detailed and " +"welcomed\n" +"`feedback `_.\n" +"\n" +"Do not contact contributors directly about support or help with technical " +"issues.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Authors\n" +"-------\n" +"\n" +"* initOS GmbH\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"- Florian Kantelberg \n" +"- `Tecnativa `__:\n" +"\n" +" - Carlos Roca\n" +"\n" +"Maintainers\n" +"-----------\n" +"\n" +"This module is maintained by the OCA.\n" +"\n" +".. image:: https://odoo-community.org/logo.png\n" +" :alt: Odoo Community Association\n" +" :target: https://odoo-community.org\n" +"\n" +"OCA, or the Odoo Community Association, is a nonprofit organization whose\n" +"mission is to support the collaborative development of Odoo features and\n" +"promote its widespread use.\n" +"\n" +"This module is part of the `OCA/server-auth `_ project on GitHub.\n" +"\n" +"You are welcome to contribute. To learn how please visit https://odoo-" +"community.org/page/Contribute.\n" +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 "" +"È 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 +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract_field +msgid "Abstract model to implement basic fields for encryption" +msgstr "Modello astratto per implementare campi base per la criptazione" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract +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 +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/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 "Annulla" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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/controller.esm.js:0 +msgid "Confirm" +msgstr "Conferma" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +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/wizards/vault_send_wizard.py:0 +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 +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Created via %(ip)s" +msgstr "Creato da %(ip)s" + +#. 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 +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Discard" +msgstr "Abbandona" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:vault.field_res_users__display_name +#: 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_abstract__display_name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +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/backend/templates.xml:0 +msgid "Enter" +msgstr "Inserire" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +#: model:ir.model,name:vault.model_vault_entry +msgid "Entry inside a vault" +msgstr "Voce in un deposito di sicurezza" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +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 +msgid "Export to file" +msgstr "Esporta in un file" + +#. module: vault +#: model:ir.model,name:vault.model_vault_export_wizard +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 +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 +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 +msgid "Failed to import keys from database" +msgstr "Fall'ita l'importazione delle chiavi dal database" + +#. module: vault +#: model:ir.model,name:vault.model_vault_field +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 +#: model:ir.model,name:vault.model_vault_file +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 +msgid "Generate" +msgstr "Genera" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Generate a new secret:" +msgstr "Genera un nuovo segreto:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Hide" +msgstr "Nascondi" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__id +#: model:ir.model.fields,field_description:vault.field_res_users__id +#: 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_abstract__id +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +msgid "Import from file" +msgstr "Importa da file" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard +msgid "Import wizard for vaults" +msgstr "Procedura guidata importazione depositi di sicurezza" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard_path +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 +msgid "Information" +msgstr "Informazioni" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +msgid "Invalid file to import from" +msgstr "File da cui importare non valido" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +msgid "Invalid model to store the secret" +msgstr "Modello non valido per archiviare il segreto" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +msgid "Invalid parameter" +msgstr "Parametro non valido" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +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 +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__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 +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 +#: model:ir.model,name:vault.model_vault_log +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 +msgid "Missing filename" +msgstr "Nome file mancante" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +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 +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 +msgid "No secret found" +msgstr "Nessun segreto trovato" + +#. 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 "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 +#: model:ir.module.module,summary:base.module_vault +msgid "Password vault integration in Odoo" +msgstr "Integrazione di un deposito di password in Odoo" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +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 +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 +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 +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 +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 +msgid "Save As..." +msgstr "Salva come..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +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 +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Select at least one character set" +msgstr "Selezionare l'ultimo set caratteri" + +#. 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 +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 +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 +msgid "Show" +msgstr "Mostra" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +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 +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 +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 +#: 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 "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 +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 +msgid "The following entries are broken:" +msgstr "Le seguenti voci sono rovinate:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The inbox can't be written anymore" +msgstr "Non è più possibile scrivere nel deposito in arrivo" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "The passwords aren't matching" +msgstr "Le password non corrispondono" + +#. 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 "" +"L'operazione richiesta non può essere completata per restrizioni di " +"sicurezza." + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The secret is too big" +msgstr "Il segreto è troppo grande" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +msgid "The tag must be unique!" +msgstr "L'etichetta deve essere univoca!" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +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 +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 +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Use password" +msgstr "Usa password" + +#. 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 +#: model:ir.model,name:vault.model_res_users_key +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 +#: 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.module.module,shortdesc:base.module_vault +#: 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 "Deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +msgid "Vault Field" +msgstr "Campo deposito di sicurezza" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +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 +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 +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-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +msgid "Vault export file" +msgstr "File esportazione deposito di sicurezza" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox_log +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 +msgid "Vault is not supported" +msgstr "Il deposito di sicurezza non è supportato" + +#. module: vault +#: model:ir.model,name:vault.model_vault_right +msgid "Vault rights" +msgstr "Autorizzazioni deposito di sicurezza" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox +msgid "Vault share incoming secrets" +msgstr "Segreti in ingresso condivisione deposito di sicurezza" + +#. module: vault +#: model:ir.model,name:vault.model_vault_tag +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 +msgid "Warning" +msgstr "Attenzione" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "With Content" +msgstr "Con contenuto" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Without Content" +msgstr "Senza contanuto" + +#. module: vault +#: model:ir.model,name:vault.model_vault_store_wizard +msgid "Wizard store a shared secret in a vault" +msgstr "" +"La procedura guidata salva un segreto condiviso in un deposito di sicurezza" + +#. module: vault +#: model:ir.model,name:vault.model_vault_send_wizard +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 +msgid "Written via %(ip)s" +msgstr "Scritto da %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +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 +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't change the inbox of other users." +msgstr "Non è possibile modificare il deposito in arrivo di altri utenti." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't invalidate the key of other users." +msgstr "Non è possibile invalidare la chiave di altri utenti." + +#. 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 +msgid "a-z" +msgstr "a-z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "error" +msgstr "errore" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "of entry" +msgstr "dell'ingresso" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "warn" +msgstr "attenzione" diff --git a/vault/i18n/nl.po b/vault/i18n/nl.po new file mode 100644 index 0000000000..c8e174fb12 --- /dev/null +++ b/vault/i18n/nl.po @@ -0,0 +1,1553 @@ +# 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" +"POT-Creation-Date: 2026-10-29 10:55+0000\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 +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "%(action)s toegang %(name)s door %(user)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%s (copy)" +msgstr "%s (kopie)" + +#. module: vault +#: model:ir.module.module,description:base.module_vault +msgid "" +".. image:: https://odoo-community.org/readme-banner-image\n" +" :target: https://odoo-community.org/get-involved?utm_source=readme\n" +" :alt: Odoo Community Association\n" +"\n" +"=====\n" +"Vault\n" +"=====\n" +"\n" +".. \n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! This file is generated by oca-gen-addon-readme !!\n" +" !! changes will be overwritten. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! source digest: " +"sha256:4c1e235277a22d56ad27351359792d02ea259f8824ebcd1fcfdbc9183e179c28\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +"\n" +".. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png\n" +" :target: https://odoo-community.org/page/development-status\n" +" :alt: Beta\n" +".. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html\n" +" :alt: License: AGPL-3\n" +".. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-" +"lightgray.png?logo=github\n" +" :target: https://github.com/OCA/server-auth/tree/20.0/vault\n" +" :alt: OCA/server-auth\n" +".. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-" +"F47D42.png\n" +" :target: https://translation.odoo-community.org/projects/server-" +"auth-20-0/server-auth-20-0-vault\n" +" :alt: Translate me on Weblate\n" +".. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B." +"png\n" +" :target: https://runboat.odoo-community.org/builds?repo=OCA/server-" +"auth&target_branch=20.0\n" +" :alt: Try me on Runboat\n" +"\n" +"|badge1| |badge2| |badge3| |badge4| |badge5|\n" +"\n" +"This module implements a vault for secrets and files using\n" +"end-to-end-encryption. The encryption and decryption happens in the\n" +"browser using a vault specific shared master key. The master keys are\n" +"encrypted using asymmetrically. For this the user has to enter a second\n" +"password on the first login or if he needs to access data in a vault.\n" +"The asymmetric keys are stored for a certain time in the browser\n" +"storage.\n" +"\n" +"The server can never access the secrets with the information available.\n" +"Only people registered in the vault can decrypt or encrypt values in a\n" +"vault. The meta data isn't encrypted to be able to search/filter for\n" +"entries more easily.\n" +"\n" +"This modules requires a secure context for the browser to work properly\n" +"and therefore HTTPS support is required.\n" +"\n" +"The `vault-recovery `__\n" +"project focuses on disaster recovery in case of an incident to recover\n" +"secrets from old database backups or old exports.\n" +"\n" +"**Table of contents**\n" +"\n" +".. contents::\n" +" :local:\n" +"\n" +"Known issues / Roadmap\n" +"======================\n" +"\n" +"- Field and file history for restoration\n" +"- Import improvement\n" +"\n" +"..\n" +"\n" +" - Support challenge-response/FIDO2\n" +" - Support for argon2 and kdbx v4\n" +"\n" +"- When changing an entry from one vault to another existing vault, the\n" +" values added on this entry cannot be accessed, so the field vault is\n" +" going to be readonly when it is defined.\n" +"\n" +" If you want to move entries between vaults you can use the export ->\n" +" import option.\n" +"\n" +"- HTTPS or localhost (secure browser context) is required for the client\n" +" side encryption\n" +"\n" +"Bug Tracker\n" +"===========\n" +"\n" +"Bugs are tracked on `GitHub Issues `_.\n" +"In case of trouble, please check there if your issue has already been " +"reported.\n" +"If you spotted it first, help us to smash it by providing a detailed and " +"welcomed\n" +"`feedback `_.\n" +"\n" +"Do not contact contributors directly about support or help with technical " +"issues.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Authors\n" +"-------\n" +"\n" +"* initOS GmbH\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"- Florian Kantelberg \n" +"- `Tecnativa `__:\n" +"\n" +" - Carlos Roca\n" +"\n" +"Maintainers\n" +"-----------\n" +"\n" +"This module is maintained by the OCA.\n" +"\n" +".. image:: https://odoo-community.org/logo.png\n" +" :alt: Odoo Community Association\n" +" :target: https://odoo-community.org\n" +"\n" +"OCA, or the Odoo Community Association, is a nonprofit organization whose\n" +"mission is to support the collaborative development of Odoo features and\n" +"promote its widespread use.\n" +"\n" +"This module is part of the `OCA/server-auth `_ project on GitHub.\n" +"\n" +"You are welcome to contribute. To learn how please visit https://odoo-" +"community.org/page/Contribute.\n" +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 "" +"Een beveiligde browsercontext is vereist. Schakel over naar https of neem " +"contact op met uw beheerder" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "A-Z" +msgstr "A-Z" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract_field +msgid "Abstract model to implement basic fields for encryption" +msgstr "Abstract model om basisvelden voor encryptie te implementeren" + +#. module: vault +#: model:ir.model,name:vault.model_vault_abstract +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 "" +"Alle gebruikers toestaan de kluizen waartoe ze toegang hebben te exporteren" + +#. 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 "" +"Alle gebruikers toestaan de kluizen waartoe ze toegang hebben te importeren" + +#. 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 "Het delen van geheimen met externe gebruikers toestaan" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_create +msgid "Allow to create in the vault" +msgstr "Aanmaken in de kluis toestaan" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_delete +msgid "Allow to delete a vault" +msgstr "Verwijderen van een kluis toestaan" + +#. module: vault +#: model:res.groups,name:vault.group_vault_export +msgid "Allow to export vaults" +msgstr "Exporteren van kluizen toestaan" + +#. module: vault +#: model:res.groups,name:vault.group_vault_import +msgid "Allow to import vaults" +msgstr "Importeren van kluizen toestaan" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_share +msgid "Allow to share a vault with new users" +msgstr "Delen van een kluis met nieuwe gebruikers toestaan" + +#. module: vault +#: model:ir.model.fields,help:vault.field_vault_right__perm_write +msgid "Allow to write to the vault" +msgstr "Schrijven naar de kluis toestaan" + +#. 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 "Aanmaken toegestaan" + +#. 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 "Verwijderen toegestaan" + +#. 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 "Lezen toegestaan" + +#. 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 "Delen toegestaan" + +#. 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 "Schrijven toegestaan" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 "Per kluis" + +#. 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 "Annuleer" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Characters:" +msgstr "Karakters:" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_entry__child_ids +msgid "Child" +msgstr "Onderliggend" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_form +msgid "Childs" +msgstr "Onderliggende" + +#. 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/controller.esm.js:0 +msgid "Confirm" +msgstr "Bevestigen" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 +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/wizards/vault_send_wizard.py:0 +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 +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Created via %(ip)s" +msgstr "Aangemaakt via %(ip)s" + +#. 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 "Aangepast JSON-formaat .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 "Verwijder" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Discard" +msgstr "Negeren" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:vault.field_res_users__display_name +#: 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_abstract__display_name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +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/backend/templates.xml:0 +msgid "Enter" +msgstr "Invoeren" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 "Item" + +#. module: vault +#: model:ir.model,name:vault.model_vault_entry +msgid "Entry inside a vault" +msgstr "Item in een kluis" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +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 "Kluizen exporteren" + +#. 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 "Export naar bestand" + +#. module: vault +#: model:ir.model,name:vault.model_vault_export_wizard +msgid "Export wizard for vaults" +msgstr "Exportwizard voor kluizen" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to export keys to object store" +msgstr "Exporteren van sleutels naar de objectopslag mislukt" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to export the keys to the database" +msgstr "Exporteren van de sleutels naar de database mislukt" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/vault.esm.js:0 +msgid "Failed to import keys from database" +msgstr "Kan geen sleutels uit database importeren" + +#. module: vault +#: model:ir.model,name:vault.model_vault_field +msgid "Field of a vault" +msgstr "Veld van een kluis" + +#. 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 +#: model:ir.model,name:vault.model_vault_file +msgid "File of a vault" +msgstr "Bestand van een kluis" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "File to share:" +msgstr "Te delen bestand:" + +#. 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 +msgid "Generate" +msgstr "Genereer" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Generate a new secret:" +msgstr "Genereer een nieuw geheim:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +msgid "Hide" +msgstr "Verbergen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_config_settings__id +#: model:ir.model.fields,field_description:vault.field_res_users__id +#: 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_abstract__id +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 "" +"Als de inbox verlopen is, kan er niet via de link naar geschreven worden" + +#. 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 "Als dit 0 is, kan er niet via de link naar de inbox geschreven worden" + +#. 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 "Kluizen importeren" + +#. 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 "Importeren uit bestand" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard +msgid "Import wizard for vaults" +msgstr "Wizard voor het importeren van kluizen" + +#. module: vault +#: model:ir.model,name:vault.model_vault_import_wizard_path +msgid "Import wizard path for vaults" +msgstr "Pad van de importwizard voor kluizen" + +#. 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 "Inbox ingeschakeld" + +#. 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 "Inboxlink" + +#. 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 "Onderliggende meenemen" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "Information" +msgstr "Informatie" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_import_wizard.py:0 +msgid "Invalid file to import from" +msgstr "Ongeldig bestand om uit te importeren" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_store_wizard.py:0 +msgid "Invalid model to store the secret" +msgstr "Ongeldig model om het geheim op te slaan" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users_key.py:0 +msgid "Invalid parameter" +msgstr "Ongeldige parameter" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 "Privésleutel ongeldig maken" + +#. 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 "IV" + +#. 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 +msgid "Key Management" +msgstr "Sleutelbeheer" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_send_wizard__key_user +msgid "Key User" +msgstr "Sleutel gebruiker" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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__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 +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 +#: model:ir.model,name:vault.model_vault_log +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 +msgid "Missing filename" +msgstr "Ontbrekende bestandsnaam" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +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 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 "Naam" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Name of your secret:" +msgstr "Naam van uw geheim:" + +#. module: vault +#. odoo-python +#: code:addons/vault/wizards/vault_send_wizard.py:0 +msgid "Neither a secret nor file was given" +msgstr "Er is geen geheim of bestand opgegeven" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_users_form_keys_modif +msgid "New inbox link" +msgstr "Nieuwe inboxlink" + +#. 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 +msgid "No secret found" +msgstr "Geen geheim gevonden" + +#. 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 "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 "Bovenliggend item" + +#. module: vault +#: model:ir.module.module,summary:base.module_vault +msgid "Password vault integration in Odoo" +msgstr "Integratie van een wachtwoordkluis in Odoo" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +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 "Rechten gebruiker" + +#. 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 "Voer het wachtwoord van de database in" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/import.esm.js:0 +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 +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 +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 +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 "Opnieuw versleutelen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault__reencrypt_required +msgid "Reencrypt Required" +msgstr "Opnieuw versleutelen vereist" + +#. 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 "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 +msgid "Save As..." +msgstr "Opslaan als..." + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/templates.xml:0 +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 "Tijdelijk geheim" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Secret to share:" +msgstr "Geheim om te delen:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "Select at least one character set" +msgstr "Selecteer ten minste één tekenset" + +#. 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 +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 +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 +msgid "Show" +msgstr "Toon" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +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 "Opslaan" + +#. 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 "Het geheim in een kluis opslaan" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.inbox +msgid "Submit secret" +msgstr "Geheim verzenden" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +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 +#: 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 "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 +msgid "The field is empty, there's nothing to save!" +msgstr "Het veld is leeg, er is niets om op te slaan!" + +#. 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 "De bestanden moeten eindigen op een van de ondersteunde bestandstypen:" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "The following entries are broken:" +msgstr "De volgende items zijn beschadigd:" + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The inbox can't be written anymore" +msgstr "Er kan niet meer naar de inbox geschreven worden" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/common/vault_utils_service.esm.js:0 +msgid "The passwords aren't matching" +msgstr "De wachtwoorden komen niet overeen" + +#. 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 "" +"De gevraagde bewerking kan niet worden voltooid vanwege " +"beveiligingsbeperkingen." + +#. module: vault +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The secret is too big" +msgstr "Het geheim is te groot" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_tag_name_uniq +msgid "The tag must be unique!" +msgstr "Het label moet uniek zijn!" + +#. module: vault +#: model:ir.model.constraint,message:vault.constraint_vault_right_user_uniq +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 +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 +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "Use password" +msgstr "Wachtwoord gebruiken" + +#. 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 +#: model:ir.model,name:vault.model_res_users_key +msgid "User data of a vault" +msgstr "Gebruikersgegevens van een kluis" + +#. 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 "" +"Met deze link kunt u naar de huidige inbox schrijven. Als u wilt dat mensen " +"nieuwe inboxen aanmaken, geef hen dan uw inboxlink uit uw sleutelbeheer." + +#. 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 +#: 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.module.module,shortdesc:base.module_vault +#: 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 "Kluis" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_field.esm.js:0 +msgid "Vault Field" +msgstr "Kluisveld" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_file.esm.js:0 +msgid "Vault File" +msgstr "Kluisbestand" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_field.esm.js:0 +msgid "Vault Inbox Field" +msgstr "Kluis inboxveld" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_inbox_file.esm.js:0 +msgid "Vault Inbox File" +msgstr "Kluis inboxbestand" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_res_users__vault_right_ids +msgid "Vault Right" +msgstr "Kluisrecht" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.res_config_settings_view_form +msgid "Vault Share" +msgstr "Kluis delen" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/fields/vault_export_file.esm.js:0 +msgid "Vault export file" +msgstr "Exportbestand van de kluis" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox_log +msgid "Vault inbox log" +msgstr "Log van de kluisinbox" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "Vault is not supported" +msgstr "Kluis wordt niet ondersteund" + +#. module: vault +#: model:ir.model,name:vault.model_vault_right +msgid "Vault rights" +msgstr "Kluisrechten" + +#. module: vault +#: model:ir.model,name:vault.model_vault_inbox +msgid "Vault share incoming secrets" +msgstr "Binnenkomende geheimen van de kluis delen" + +#. module: vault +#: model:ir.model,name:vault.model_vault_tag +msgid "Vault tag" +msgstr "Kluislabel" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Vaults" +msgstr "Kluizen" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_form +msgid "Verify" +msgstr "Verifiëren" + +#. 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 +msgid "Warning" +msgstr "Waarschuwing" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "With Content" +msgstr "Met inhoud" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Without Content" +msgstr "Zonder inhoud" + +#. module: vault +#: model:ir.model,name:vault.model_vault_store_wizard +msgid "Wizard store a shared secret in a vault" +msgstr "Wizard om een gedeeld geheim in een kluis op te slaan" + +#. module: vault +#: model:ir.model,name:vault.model_vault_send_wizard +msgid "Wizard to send another user a secret" +msgstr "Wizard om een geheim naar een andere gebruiker te sturen" + +#. module: vault +#: model:ir.model.fields,field_description:vault.field_vault_right__perm_write +msgid "Write" +msgstr "Schrijven" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Written via %(ip)s" +msgstr "Geschreven via %(ip)s" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "You can not create recursive entries." +msgstr "U kunt geen recursieve items aanmaken." + +#. 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 "" +"U kunt het geheim alleen verzenden naar een gebruiker die een sleutelpaar " +"heeft gegenereerd.\n" +" Als een gebruiker niet wordt getoond, vraag hem dan " +"om deze te genereren." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't change the inbox of other users." +msgstr "U kunt de inbox van andere gebruikers niet wijzigen." + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't invalidate the key of other users." +msgstr "U kunt de sleutel van andere gebruikers niet ongeldig maken." + +#. 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 "U verliest de toegang tot alle kluizen en uw inbox. Wilt u doorgaan?" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/templates.xml:0 +msgid "a-z" +msgstr "a-z" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "error" +msgstr "fout" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "of entry" +msgstr "van item" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "warn" +msgstr "waarschuwing" diff --git a/vault/i18n/vault.pot b/vault/i18n/vault.pot new file mode 100644 index 0000000000..448f2b155a --- /dev/null +++ b/vault/i18n/vault.pot @@ -0,0 +1,1522 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * vault +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 20.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-29 10:55+0000\n" +"PO-Revision-Date: 2026-10-29 10:55+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%(action)s entry %(name)s by %(user)s" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_entry.py:0 +msgid "%s (copy)" +msgstr "" + +#. module: vault +#: model:ir.module.module,description:base.module_vault +msgid "" +".. image:: https://odoo-community.org/readme-banner-image\n" +" :target: https://odoo-community.org/get-involved?utm_source=readme\n" +" :alt: Odoo Community Association\n" +"\n" +"=====\n" +"Vault\n" +"=====\n" +"\n" +".. \n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! This file is generated by oca-gen-addon-readme !!\n" +" !! changes will be overwritten. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! source digest: sha256:4c1e235277a22d56ad27351359792d02ea259f8824ebcd1fcfdbc9183e179c28\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +"\n" +".. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png\n" +" :target: https://odoo-community.org/page/development-status\n" +" :alt: Beta\n" +".. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html\n" +" :alt: License: AGPL-3\n" +".. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github\n" +" :target: https://github.com/OCA/server-auth/tree/20.0/vault\n" +" :alt: OCA/server-auth\n" +".. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png\n" +" :target: https://translation.odoo-community.org/projects/server-auth-20-0/server-auth-20-0-vault\n" +" :alt: Translate me on Weblate\n" +".. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png\n" +" :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=20.0\n" +" :alt: Try me on Runboat\n" +"\n" +"|badge1| |badge2| |badge3| |badge4| |badge5|\n" +"\n" +"This module implements a vault for secrets and files using\n" +"end-to-end-encryption. The encryption and decryption happens in the\n" +"browser using a vault specific shared master key. The master keys are\n" +"encrypted using asymmetrically. For this the user has to enter a second\n" +"password on the first login or if he needs to access data in a vault.\n" +"The asymmetric keys are stored for a certain time in the browser\n" +"storage.\n" +"\n" +"The server can never access the secrets with the information available.\n" +"Only people registered in the vault can decrypt or encrypt values in a\n" +"vault. The meta data isn't encrypted to be able to search/filter for\n" +"entries more easily.\n" +"\n" +"This modules requires a secure context for the browser to work properly\n" +"and therefore HTTPS support is required.\n" +"\n" +"The `vault-recovery `__\n" +"project focuses on disaster recovery in case of an incident to recover\n" +"secrets from old database backups or old exports.\n" +"\n" +"**Table of contents**\n" +"\n" +".. contents::\n" +" :local:\n" +"\n" +"Known issues / Roadmap\n" +"======================\n" +"\n" +"- Field and file history for restoration\n" +"- Import improvement\n" +"\n" +"..\n" +"\n" +" - Support challenge-response/FIDO2\n" +" - Support for argon2 and kdbx v4\n" +"\n" +"- When changing an entry from one vault to another existing vault, the\n" +" values added on this entry cannot be accessed, so the field vault is\n" +" going to be readonly when it is defined.\n" +"\n" +" If you want to move entries between vaults you can use the export ->\n" +" import option.\n" +"\n" +"- HTTPS or localhost (secure browser context) is required for the client\n" +" side encryption\n" +"\n" +"Bug Tracker\n" +"===========\n" +"\n" +"Bugs are tracked on `GitHub Issues `_.\n" +"In case of trouble, please check there if your issue has already been reported.\n" +"If you spotted it first, help us to smash it by providing a detailed and welcomed\n" +"`feedback `_.\n" +"\n" +"Do not contact contributors directly about support or help with technical issues.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Authors\n" +"-------\n" +"\n" +"* initOS GmbH\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"- Florian Kantelberg \n" +"- `Tecnativa `__:\n" +"\n" +" - Carlos Roca\n" +"\n" +"Maintainers\n" +"-----------\n" +"\n" +"This module is maintained by the OCA.\n" +"\n" +".. image:: https://odoo-community.org/logo.png\n" +" :alt: Odoo Community Association\n" +" :target: https://odoo-community.org\n" +"\n" +"OCA, or the Odoo Community Association, is a nonprofit organization whose\n" +"mission is to support the collaborative development of Odoo features and\n" +"promote its widespread use.\n" +"\n" +"This module is part of the `OCA/server-auth `_ project on GitHub.\n" +"\n" +"You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.\n" +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/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/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 +#. odoo-python +#: code:addons/vault/models/vault_inbox.py:0 +msgid "Created via %(ip)s" +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_config_settings__display_name +#: model:ir.model.fields,field_description:vault.field_res_users__display_name +#: 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_abstract__display_name +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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 +#. 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_config_settings__id +#: model:ir.model.fields,field_description:vault.field_res_users__id +#: 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_abstract__id +#: model:ir.model.fields,field_description:vault.field_vault_abstract_field__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/wizards/vault_store_wizard.py:0 +msgid "Invalid model to store the secret" +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 +#: model:ir.module.module,summary:base.module_vault +msgid "Password vault integration in Odoo" +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-python +#: code:addons/vault/controllers/main.py:0 +msgid "The inbox can't be written anymore" +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 +#. odoo-python +#: code:addons/vault/controllers/main.py:0 +msgid "The secret is too big" +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.module.module,shortdesc:base.module_vault +#: 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_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "With Content" +msgstr "" + +#. module: vault +#: model_terms:ir.ui.view,arch_db:vault.view_vault_entry_search +msgid "Without Content" +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 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 +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't change the inbox of other users." +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/res_users.py:0 +msgid "You can't invalidate the key of other users." +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 "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "error" +msgstr "" + +#. module: vault +#. odoo-javascript +#: code:addons/vault/static/src/backend/controller.esm.js:0 +msgid "of entry" +msgstr "" + +#. module: vault +#. odoo-python +#: code:addons/vault/models/vault_log.py:0 +msgid "warn" +msgstr "" diff --git a/vault/migrations/20.0.1.0.0/pre-migration.py b/vault/migrations/20.0.1.0.0/pre-migration.py new file mode 100644 index 0000000000..ef7e68ca9b --- /dev/null +++ b/vault/migrations/20.0.1.0.0/pre-migration.py @@ -0,0 +1,30 @@ +# Copyright 2026 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tools import SQL + +# Binary fields stored in a column (attachment=False) used to contain the base64 +# encoded text. Since 20.0 the ORM stores the raw bytes, so decode the old values. +# Values which aren't plain base64 text are left untouched. +COLUMNS = [ + ("vault_file", "value"), + ("vault_inbox", "secret_file"), +] + + +def migrate(cr, version): + for table, column in COLUMNS: + col = SQL.identifier(column) + cr.execute( + SQL( + """ + UPDATE %(table)s + SET %(col)s = decode(encode(%(col)s, 'escape'), 'base64') + WHERE %(col)s IS NOT NULL + AND encode(%(col)s, 'escape') ~ %(pattern)s + """, + table=SQL.identifier(table), + col=col, + pattern=r"^[A-Za-z0-9+/=\s]+$", + ) + ) 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..91ed321012 --- /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( + self.env._( + "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..0518eb1e46 --- /dev/null +++ b/vault/models/res_users.py @@ -0,0 +1,104 @@ +# © 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 +from odoo.exceptions import AccessError + +_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) + # The token gives write access to the inbox of the user + inbox_token = fields.Char( + default=lambda self: uuid4(), + readonly=True, + groups="base.group_erp_manager", + ) + + @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 + + def _can_manage_vault_of(self, user): + """Only the user itself or user administrators can manage the vault + settings of an user""" + return ( + self.env.su + or user == self.env.user + or self.env.user.has_group("base.group_erp_manager") + ) + + @api.depends("inbox_token") + @api.depends_context("uid") + def _compute_inbox_link(self): + base_url = self.env["ir.config_parameter"].sudo().get_str("web.base.url") + for rec in self: + if self._can_manage_vault_of(rec._origin): + rec.inbox_link = f"{base_url}/vault/inbox/{rec.sudo().inbox_token}" + else: + rec.inbox_link = False + + @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() + if not self._can_manage_vault_of(self): + raise AccessError(self.env._("You can't change the inbox of other users.")) + 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() + if not self.env.su and self != self.env.user: + raise AccessError( + self.env._("You can't invalidate the key of other users.") + ) + self.keys.write({"current": False}) + self.vault_right_ids.sudo().unlink() + self.inbox_ids.unlink() + self.env["vault"].search([])._compute_access() # pylint: disable=no-search-all + 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..bcfbe5dd36 --- /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(self.env._("Invalid parameter")) + + if not isinstance(iterations, int) or iterations < 4000: + raise ValidationError(self.env._("Invalid parameter")) + + if not isinstance(version, int): + raise ValidationError(self.env._("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..70cf58d336 --- /dev/null +++ b/vault/models/vault.py @@ -0,0 +1,162 @@ +# © 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", 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() + + _uuid_uniq = models.Constraint( + "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.allowed_create = True + rec.allowed_share = True + rec.allowed_write = True + rec.allowed_delete = True + rec.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": self.env._("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": self.env._("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..8f74d143d7 --- /dev/null +++ b/vault/models/vault_entry.py @@ -0,0 +1,218 @@ +# © 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 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", + ondelete="cascade", + domain="[('vault_id', '=', vault_id)]", + ) + child_ids = fields.One2many("vault.entry", "parent_id") + + vault_id = fields.Many2one("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", 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, + ) + + _vault_uuid_uniq = models.Constraint( + "UNIQUE(vault_id, uuid)", + "The UUID must be unique.", + ) + + @api.constrains("parent_id") + def _check_parent_id(self): + if self._has_cycle(): + raise ValidationError(self.env._("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"] = self.env._("%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 = fields.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", "<", fields.Datetime.now())] + + return [ + "|", + ("expire_date", ">=", fields.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( + self.env._( + "%(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": self.env._("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": self.env._("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..fc7a42c770 --- /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..1d1890b025 --- /dev/null +++ b/vault/models/vault_file.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 VaultFile(models.Model): + _name = "vault.file" + _description = "File of a vault" + _order = "name" + _inherit = ("vault.abstract.field", "vault.abstract") + + value = fields.Binary(attachment=False) diff --git a/vault/models/vault_inbox.py b/vault/models/vault_inbox.py new file mode 100644 index 0000000000..c776ea99b1 --- /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 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: fields.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", readonly=True) + + _value_check = models.Constraint( + "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_str("web.base.url") + for rec in self: + rec.inbox_link = f"{base_url}/vault/inbox/{rec.token}" + + @api.model + def find_inbox(self, token): + return self.search([("token", "=", token)]) + + def _is_writable(self): + """Check if the inbox can still be written using the link""" + self.ensure_one() + return self.accesses > 0 and ( + not self.expiration or fields.Datetime.now() < self.expiration + ) + + def store_in_inbox( + self, + name, + secret, + secret_file, + iv, + key, + user, + filename, + ip=None, + ): + # The sender is anonymous, only the IP is known + ip = ip or "n/a" + if len(self) == 0: + log = self.env._("Created via %(ip)s", ip=ip) + 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._is_writable(): + log = self.env._("Written via %(ip)s", ip=ip) + + 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 + return self.browse() 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..1caebe3dc9 --- /dev/null +++ b/vault/models/vault_log.py @@ -0,0 +1,44 @@ +# © 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", + ondelete="cascade", + required=True, + readonly=True, + ) + entry_id = fields.Many2one( + "vault.entry", + ondelete="cascade", + readonly=True, + ) + user_id = fields.Many2one("res.users", 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", self.env._("Information")), + ("warn", self.env._("Warning")), + ("error", self.env._("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..eebec328dc --- /dev/null +++ b/vault/models/vault_right.py @@ -0,0 +1,109 @@ +# © 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", + 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", + 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() + + _user_uniq = models.Constraint( + "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.env.su and res.filtered(lambda r: not r.allowed_share): + 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..5d3a24506a --- /dev/null +++ b/vault/models/vault_tag.py @@ -0,0 +1,17 @@ +# © 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) + + _name_uniq = models.Constraint( + "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.access.csv b/vault/security/ir.access.csv new file mode 100644 index 0000000000..521f5fe446 --- /dev/null +++ b/vault/security/ir.access.csv @@ -0,0 +1,18 @@ +id,name,model_id,group_id/id,operation,domain +vault_access_default,vault.access.default,vault,base.group_user,crud,"['|', ('user_id', '=', user.id), ('right_ids.user_id', '=', user.id)]" +vault_entry_access_default,vault.entry.access.default,vault.entry,base.group_user,crud,"['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)]" +access_vault_export_wizard,access_vault_export_wizard,vault.export.wizard,base.group_user,crud, +vault_field_access_default,vault.field.access.default,vault.field,base.group_user,crud,"['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)]" +vault_file_access_default,vault.file.access.default,vault.file,base.group_user,crud,"['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)]" +access_vault_import_wizard,access_vault_import_wizard,vault.import.wizard,base.group_user,crud, +access_vault_import_wizard_path,access_vault_import_wizard_path,vault.import.wizard.path,base.group_user,crud, +access_vault_inbox,access_vault_inbox,vault.inbox,base.group_user,crud, +vault_inbox_owner,vault.inbox.access.owner,vault.inbox,,crud,"[('user_id', '=', user.id)]" +access_vault_inbox_log,access_vault_inbox_log,vault.inbox.log,base.group_user,crud, +vault_inbox_log_owner,vault.inbox.log.access.owner,vault.inbox.log,,crud,"[('inbox_id.user_id', '=', user.id)]" +vault_log_access_default,vault.log.access.read,vault.log,base.group_user,r,"['|', ('vault_id.user_id', '=', user.id), ('vault_id.right_ids.user_id', '=', user.id)]" +vault_right_access_default,vault.right.access.default,vault.right,base.group_user,crud,"[('vault_id.right_ids.user_id', '=', user.id)]" +access_vault_send_wizard,access_vault_send_wizard,vault.send.wizard,base.group_user,crud, +access_vault_store_wizard,access_vault_store_wizard,vault.store.wizard,base.group_user,crud, +access_vault_tag,access_vault_tag,vault.tag,base.group_user,crud, +res_users_key_access_default,res.users.key.access.default,res.users.key,base.group_user,crud,"[('user_id', '=', user.id)]" diff --git a/vault/security/vault_security.xml b/vault/security/vault_security.xml new file mode 100644 index 0000000000..914acd33f5 --- /dev/null +++ b/vault/security/vault_security.xml @@ -0,0 +1,11 @@ + + + + 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..ce838e1f29 --- /dev/null +++ b/vault/static/description/index.html @@ -0,0 +1,474 @@ + + + + + +Vault + + + +
+ + + +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"] + ); + + for (const rec of records) { + const val = await self.vault_utils.sym_decrypt( + current_key, + getContent(rec.value), + rec.iv + ); + if (val === null) { + const fixed_text = _t("of entry"); + problems.push( + `${type} '${rec.name}' ${fixed_text} '${rec.entry_name}'` + ); + continue; + } + + const iv = self.vault_utils.generate_iv_base64(); + const encrypted = await self.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"] + ); + + 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(getContent(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.id}; + 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"); + super.setup(); + if (this.props.resModel === "vault" && !this.vault_utils.supported()) { + this.canCreate = false; + this.canEdit = false; + } + + 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.activeActions = {...this.activeActions, create: 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..59f13e4a61 --- /dev/null +++ b/vault/static/src/backend/fields/templates.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+ + + + +
+
+
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..e6abeb944c --- /dev/null +++ b/vault/static/src/backend/vault.esm.js @@ -0,0 +1,414 @@ +// © 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 { + /** + * 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 or generate them if missing + if (!(await this._import_from_database())) { + if (await this._check_database()) + throw Error(_t("Failed to import keys from database")); + return await this.generate_keys(); + } + + // 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..8384186124 --- /dev/null +++ b/vault/static/src/common/vault_utils_service.esm.js @@ -0,0 +1,149 @@ +import {Component, onMounted, proxy, signal, t, useProps} 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}; + props = useProps({ + close: t.function(), + onResolve: t.function(), + title: t.any().optional(), + confirm: t.boolean().optional(false), + }); + keyfileInput = signal.ref(); + + setup() { + this.state = proxy({ + password: "", + confirm: "", + error: "", + }); + } + + async onConfirm() { + const {confirm} = this.props; + const password = this.state.password; + let keyfileContent = null; + const input = this.keyfileInput(); + 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.close(); + } +} + +export class GeneratePassDialog extends Component { + static template = "vault.GeneratePassDialog"; + static components = {Dialog}; + props = useProps({ + close: t.function(), + onResolve: t.function(), + title: t.any().optional(), + }); + + setup() { + this.state = proxy({ + 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.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) => { + dialog.add(AskPassDialog, { + ...props, + onResolve: resolve, + }); + }); + } + + function generate_pass(title, options = {}) { + const props = {title, ...options}; + return new Promise((resolve) => { + dialog.add(GeneratePassDialog, { + ...props, + onResolve: resolve, + }); + }); + } + + 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.test.js b/vault/static/tests/vault.test.js new file mode 100644 index 0000000000..8e3a23c461 --- /dev/null +++ b/vault/static/tests/vault.test.js @@ -0,0 +1,237 @@ +// © 2021 Florian Kantelberg - initOS GmbH +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import {after, beforeEach, describe, expect, test} from "@odoo/hoot"; +import { + getService, + makeTestApp, + onRpc, + patchWithCleanup, +} from "@web/../tests/web_test_helpers"; +import utils from "@vault/common/utils.esm"; + +describe.current.tags("headless"); + +const SUBTLE_METHODS = [ + "decrypt", + "deriveKey", + "digest", + "encrypt", + "exportKey", + "generateKey", + "importKey", + "unwrapKey", + "wrapKey", +]; + +/** + * Hoot replaces `window.crypto` with a minimal mock. The vault needs a working + * SubtleCrypto, so the methods of the mocked object are replaced by the ones of + * a real SubtleCrypto taken from an iframe. Keys are created in the iframe realm + * which is why `instanceof CryptoKey` can't be used in the assertions. + */ +beforeEach(() => { + const iframe = document.createElement("iframe"); + document.body.append(iframe); + const realSubtle = iframe.contentWindow.crypto.subtle; + const mockedSubtle = window.crypto.subtle; + const original = {...mockedSubtle}; + for (const method of SUBTLE_METHODS) + mockedSubtle[method] = realSubtle[method].bind(realSubtle); + after(() => { + for (const method of SUBTLE_METHODS) delete mockedSubtle[method]; + Object.assign(mockedSubtle, original); + iframe.remove(); + }); +}); + +function expectCryptoKey(key, type) { + expect(key.constructor.name).toBe("CryptoKey"); + expect(key.type).toBe(type); +} + +function expectKeyPair(keys) { + expectCryptoKey(keys.publicKey, "public"); + expectCryptoKey(keys.privateKey, "private"); +} + +/** + * Start the services with a mocked password dialog and mocked key routes + */ +async function makeVaultApp() { + let storedKeys = {}; + onRpc("/vault/keys/get", () => storedKeys); + onRpc("/vault/keys/store", async (request) => { + const {params} = await request.json(); + storedKeys = {...params, uuid: "42"}; + return storedKeys.uuid; + }); + await makeTestApp(); + patchWithCleanup(getService("vault_utils"), { + askpass: async () => ({password: "test", keyfile: ""}), + }); +} + +describe("utils", () => { + test("conversion utils", () => { + let text = "hello world"; + let buf = utils.fromBinary(text); + expect(buf).toBeInstanceOf(ArrayBuffer); + expect(utils.toBinary(buf)).toBe(text); + expect(utils.toBinary(false)).toBe(""); + + text = "ImhlbGxvIHdvcmxkIg=="; + buf = utils.fromBase64(text); + expect(buf).toBeInstanceOf(ArrayBuffer); + expect(utils.toBase64(buf)).toBe(text); + expect(utils.toBase64(false)).toBe(""); + + expect(utils.capitalize("hello world")).toBe("Hello World"); + }); + + test("generation utils", async () => { + let data = utils.generate_bytes(5); + expect(data).toBeInstanceOf(Uint8Array); + expect(data.length).toBe(5); + data = utils.generate_bytes(10); + expect(data.length).toBe(10); + + data = utils.generate_iv_base64(); + expect(typeof data).toBe("string"); + expect(data).not.toBe(utils.generate_iv_base64()); + + data = await utils.generate_key(); + expectCryptoKey(data, "secret"); + + expectKeyPair(await utils.generate_key_pair()); + + data = utils.generate_secret(10, "01"); + expect(data.length).toBe(10); + expect([...data].every((c) => "01".includes(c))).toBe(true); + }); + + test("asymmetric encryption", async () => { + const text = "hello world"; + const key = await utils.generate_key_pair(); + + const crypted = await utils.asym_encrypt(key.publicKey, text); + expect(typeof crypted).toBe("string"); + expect(await utils.asym_decrypt(key.privateKey, crypted)).toBe(text); + }); + + test("symmetric encryption", async () => { + 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); + expect(typeof crypted).toBe("string"); + expect(await utils.sym_decrypt(key, crypted, iv)).toBe(text); + }); + + test("import/export of keys", async () => { + const key = await utils.generate_key_pair(); + const text = "hello world"; + + let exported = await utils.export_public_key(key.publicKey); + const publicKey = await utils.load_public_key(exported); + const crypted = await utils.asym_encrypt(publicKey, text); + expect(await utils.asym_decrypt(key.privateKey, crypted)).toBe(text); + + const iv = utils.generate_bytes(12); + 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); + const privateKey = await utils.load_private_key( + exported, + wrapper, + utils.toBase64(iv) + ); + expect(await utils.asym_decrypt(privateKey, crypted)).toBe(text); + + const master_key = await utils.generate_key(); + exported = await utils.wrap(master_key, key.publicKey); + const unwrapped = await utils.unwrap(exported, key.privateKey); + const sym_iv = utils.generate_iv_base64(); + const sym_crypted = await utils.sym_encrypt(master_key, text, sym_iv); + expect(await utils.sym_decrypt(unwrapped, sym_crypted, sym_iv)).toBe(text); + }); +}); + +describe("services", () => { + beforeEach(makeVaultApp); + + test("vault keys are generated and reloaded", async () => { + const vault = getService("vault"); + + await vault._initialize_keys(); + expectKeyPair(vault.keys); + + vault.keys = undefined; + await vault._import_from_database(); + expectKeyPair(vault.keys); + }); + + test("key generation on first usage", async () => { + const vault = getService("vault"); + + // A first-time user has no keys yet. Accessing the public key + // must generate them instead of throwing. + await vault.get_public_key(); + expectKeyPair(vault.keys); + }); + + test("importer/exporter", async () => { + // 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, + ], + }; + + const master_key = await utils.generate_key(); + const imported = await getService("vault_import").import( + master_key, + "test.json", + JSON.stringify(data) + ); + const exported = await getService("vault_export").export( + master_key, + "test.json", + JSON.stringify(imported) + ); + expect(exported.type).toBe("encrypted"); + + const pass = await utils.derive_key( + "test", + utils.fromBase64(exported.salt), + exported.iterations + ); + const decrypted = JSON.parse( + await utils.sym_decrypt(pass, exported.data, exported.iv) + ); + expect(decrypted).toEqual(data.data); + }); +}); diff --git a/vault/tests/__init__.py b/vault/tests/__init__.py new file mode 100644 index 0000000000..7abca0f1f0 --- /dev/null +++ b/vault/tests/__init__.py @@ -0,0 +1,12 @@ +# © 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_security, + 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..e31ae4dc96 --- /dev/null +++ b/vault/tests/test_controller.py @@ -0,0 +1,232 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +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.http_routing.tests.common import MockRequest + +from ..controllers import main + +_logger = logging.getLogger(__name__) + + +class TestController(BaseCommon): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + @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": base64.b64encode(b"old file").decode(), + "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": base64.b64encode(b"file").decode(), + } + ) + 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.content, 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.content, 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": base64.b64encode(b"hello").decode(), + } + ) + right.write({"key": "invalid"}) + + self.controller.vault_replace(None) + self.assertEqual(field.value, "hello") + self.assertEqual(file.value.content, 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": base64.b64encode(b"test").decode(), + }, + {"model": right._name, "id": right.id, "key": "changed"}, + ] + ) + self.assertEqual(field.value, "test") + self.assertEqual(file.value.content, 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..50741717e1 --- /dev/null +++ b/vault/tests/test_inbox.py @@ -0,0 +1,119 @@ +# © 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 fields + +from odoo.addons.base.tests.common import BaseCommon + +_logger = logging.getLogger(__name__) + + +class TestShare(BaseCommon): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + 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 = fields.Datetime.to_datetime("1970-01-01") + 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..fc30b0d1ac --- /dev/null +++ b/vault/tests/test_log.py @@ -0,0 +1,44 @@ +# © 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): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + 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..b4c0a6d3b5 --- /dev/null +++ b/vault/tests/test_rights.py @@ -0,0 +1,201 @@ +# © 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): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + @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() + + def test_batch_create(self): + # Batched create must not raise a singleton error + other_user = new_test_user(self.env, login="test-vault-user-2") + rights = self.env["vault.right"].create( + [ + {"vault_id": self.vault.id, "user_id": self.user.id}, + {"vault_id": self.vault.id, "user_id": other_user.id}, + ] + ) + self.assertEqual(len(rights), 2) + + def test_batch_create_no_permission(self): + # Batched create by a user without share permission must be denied + self.env["vault.right"].create( + {"vault_id": self.vault.id, "user_id": self.user.id, "perm_share": False} + ) + with self.assertRaises(AccessError): + self.env["vault.right"].with_user(self.user).create( + [ + {"vault_id": self.vault.id, "user_id": 2}, + {"vault_id": self.vault.id, "user_id": 3}, + ] + ) + + def test_sudo_create_bypasses_share_check(self): + # As superuser the share-permission guard is bypassed + right = ( + self.env["vault.right"] + .sudo() + .create( + { + "vault_id": self.vault.id, + "user_id": self.user.id, + "perm_share": False, + } + ) + ) + self.assertTrue(right) diff --git a/vault/tests/test_security.py b/vault/tests/test_security.py new file mode 100644 index 0000000000..f1d0218435 --- /dev/null +++ b/vault/tests/test_security.py @@ -0,0 +1,236 @@ +# Copyright 2026 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import json + +from odoo import fields +from odoo.exceptions import AccessDenied, AccessError, UserError +from odoo.tests import new_test_user +from odoo.tools import mute_logger + +from odoo.addons.base.tests.common import BaseCommon +from odoo.addons.http_routing.tests.common import MockRequest + +from ..controllers import main + + +class TestSecurity(BaseCommon): + _test_user_groups = () + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.alice = new_test_user( + cls.env, login="vault_alice", groups="base.group_user" + ) + cls.bob = new_test_user(cls.env, login="vault_bob", groups="base.group_user") + cls.manager = new_test_user( + cls.env, login="vault_manager", groups="base.group_erp_manager" + ) + for user in (cls.alice, cls.bob): + cls.env["res.users.key"].with_user(user).store( + 40000, "iv", f"private {user.login}", f"public {user.login}", "salt", 1 + ) + + # Vault of Alice shared with Bob without the share permission + cls.vault = cls.env["vault"].with_user(cls.alice).create({"name": "Shared"}) + cls.right_bob = ( + cls.env["vault.right"] + .with_user(cls.alice) + .create({"vault_id": cls.vault.id, "user_id": cls.bob.id}) + ) + cls.right_bob.write( + { + "perm_create": False, + "perm_write": False, + "perm_share": False, + "perm_delete": False, + } + ) + cls.entry = ( + cls.env["vault.entry"] + .with_user(cls.alice) + .create({"vault_id": cls.vault.id, "name": "Entry"}) + ) + cls.controller = main.Controller() + + # S2: actions on other users + def test_invalidate_key_of_other_user(self): + right_alice = self.vault.right_ids.filtered(lambda r: r.user_id == self.alice) + with self.assertRaises(AccessError): + self.alice.with_user(self.bob).action_invalidate_key() + self.assertTrue(right_alice.exists()) + self.assertTrue(self.alice.keys.filtered("current")) + + def test_invalidate_own_key(self): + self.bob.with_user(self.bob).action_invalidate_key() + self.assertFalse(self.right_bob.exists()) + + def test_new_inbox_token_of_other_user(self): + token = self.alice.inbox_token + with self.assertRaises(AccessError): + self.alice.with_user(self.bob).action_new_inbox_token() + self.assertEqual(self.alice.inbox_token, token) + + def test_new_inbox_token_allowed(self): + token = self.alice.inbox_token + self.alice.with_user(self.alice).action_new_inbox_token() + self.assertNotEqual(self.alice.inbox_token, token) + + token = self.alice.inbox_token + self.alice.with_user(self.manager).action_new_inbox_token() + self.assertNotEqual(self.alice.inbox_token, token) + + # S6: inbox token readable by other users + def test_inbox_token_not_readable_by_others(self): + with self.assertRaises(AccessError): + self.alice.with_user(self.bob).read(["inbox_token"]) + self.assertFalse(self.alice.with_user(self.bob).inbox_link) + + def test_inbox_link_readable_by_owner_and_manager(self): + token = self.alice.inbox_token + self.assertIn(token, self.alice.with_user(self.alice).inbox_link) + self.assertIn(token, self.alice.with_user(self.manager).inbox_link) + + # S3: store wizard with arbitrary models + def test_store_wizard_model(self): + values = { + "vault_id": self.vault.id, + "entry_id": self.entry.id, + "name": "Secret", + "iv": "iv", + "key": "k", + "secret": "secret", + "secret_temporary": "temp", + } + wizard = ( + self.env["vault.store.wizard"] + .with_user(self.alice) + .create(dict(values, model="res.partner")) + ) + with self.assertRaises(UserError): + wizard.action_store() + + wizard.model = "vault.field" + wizard.action_store() + self.assertEqual(self.entry.field_ids.name, "Secret") + + @mute_logger("odoo.addons.vault.wizards.vault_store_wizard") + def test_store_wizard_error_is_raised(self): + # Bob can't create in the vault, the error must be shown to the user + wizard = ( + self.env["vault.store.wizard"] + .with_user(self.bob) + .create( + { + "vault_id": self.vault.id, + "entry_id": self.entry.id, + "name": "Secret", + "iv": "iv", + "key": "k", + "secret": "secret", + "secret_temporary": "temp", + "model": "vault.field", + } + ) + ) + with self.assertRaises(AccessError): + wizard.action_store() + + # S4: replace route with arbitrary models + def test_replace_invalid_model(self): + partner = self.env["res.partner"].create({"name": "Partner"}) + with MockRequest(self.env(user=self.alice)): + with self.assertRaises(AccessDenied): + self.controller.vault_replace( + [{"model": "res.partner", "id": partner.id, "name": "Hacked"}] + ) + with self.assertRaises(AccessDenied): + self.controller.vault_replace( + [{"model": "vault.field", "id": 999999999, "value": "test"}] + ) + self.assertEqual(partner.name, "Partner") + + # S7: public inbox + def _inbox_request(self, token, **params): + def render(template, context): + return json.dumps(context) + + with MockRequest(self.env) as request_mock: + request_mock.render = render + request_mock.httprequest.method = "POST" + request_mock.params = { + "name": "Name", + "encrypted": "secret", + "iv": "iv", + "key": "key", + **params, + } + return json.loads(self.controller.vault_inbox(token).data) + + def test_inbox_expired(self): + inbox = self.env["vault.inbox"].create( + { + "user_id": self.alice.id, + "name": "Inbox", + "key": "key", + "iv": "iv", + "secret": "old", + "accesses": 5, + "expiration": fields.Datetime.to_datetime("2000-01-01"), + } + ) + response = self._inbox_request(inbox.token) + self.assertIn("error", response) + self.assertNotIn("message", response) + self.assertEqual(inbox.secret, "old") + + def test_inbox_log(self): + inbox = self.env["vault.inbox"].create( + { + "user_id": self.alice.id, + "name": "Inbox", + "key": "key", + "iv": "iv", + "secret": "old", + "accesses": 5, + } + ) + response = self._inbox_request(inbox.token) + self.assertIn("message", response) + self.assertEqual(inbox.secret, "secret") + self.assertNotIn("False", inbox.log_ids[:1].name) + + def test_inbox_max_size(self): + self.env["ir.config_parameter"].set_int("vault.inbox_max_size", 10) + response = self._inbox_request( + self.alice.inbox_token, + encrypted="", + encrypted_file=base64.b64encode(b"x" * 100).decode(), + filename="file.txt", + ) + self.assertIn("error", response) + self.assertFalse( + self.env["vault.inbox"].search([("user_id", "=", self.alice.id)]) + ) + + def test_send_wizard_log(self): + wizard = ( + self.env["vault.send.wizard"] + .with_user(self.alice) + .create( + { + "name": "Sent secret", + "iv": "iv", + "key_user": "key", + "key": "k", + "secret": "secret", + "user_id": self.bob.id, + } + ) + ) + wizard.action_send() + inbox = self.env["vault.inbox"].search([("name", "=", "Sent secret")]) + self.assertIn(self.alice.name, inbox.log_ids.name) + self.assertNotIn(self.bob.name, inbox.log_ids.name) diff --git a/vault/tests/test_user.py b/vault/tests/test_user.py new file mode 100644 index 0000000000..1585fd96b4 --- /dev/null +++ b/vault/tests/test_user.py @@ -0,0 +1,63 @@ +# © 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): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + 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..8b099ea8e0 --- /dev/null +++ b/vault/tests/test_vault.py @@ -0,0 +1,171 @@ +# © 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): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + @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..818caaa447 --- /dev/null +++ b/vault/tests/test_widgets.py @@ -0,0 +1,161 @@ +# © 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": "SGVsbG8gV29ybGQ=", "iv": "abcd"}, + {"name": "secret", "value": "ZGxyb3cgb2xsZWg=", "iv": "abcd"}, + {}, + ], + "childs": [ + { + "uuid": "42aa", + "note": "test note subchild", + "name": "test subchild", + "url": "subchild.example.org", + "fields": [], + "files": [], + "childs": [], + }, + TestChild, + ], + }, + TestChild, +] + + +class TestWidgets(BaseCommon): + # Run the tests with the user creating the test data like in previous versions + _test_user_groups = () + + @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.decode()) + 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.decode()) + 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.decode()) + 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..cc05694426 --- /dev/null +++ b/vault/views/menuitems.xml @@ -0,0 +1,65 @@ + + + + Vault + vault + list,form + + + + All Entries + vault.entry + list,form + {'search_default_vault': 1, 'search_default_with_content': 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..df25fcdea6 --- /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..996d76f96c --- /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..eb393634cb --- /dev/null +++ b/vault/views/vault_entry_views.xml @@ -0,0 +1,240 @@ + + + + vault.entry + + + + + + + + + + + vault.entry + + + + + + + + + + + + vault.entry + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + vault.entry + primary + + + + + 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..43e628db92 --- /dev/null +++ b/vault/views/vault_right_views.xml @@ -0,0 +1,77 @@ + + + + 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..ee62dd3014 --- /dev/null +++ b/vault/views/vault_views.xml @@ -0,0 +1,107 @@ + + + + vault.search + vault + + + + + + + + + + Entries + vault.entry + list,form + [("vault_id", "=", active_id)] + { + "default_vault_id": active_id, + "searchpanel_default_vault_id": active_id, + "search_default_with_content": 1} + + + + + + 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..cd05c81f27 --- /dev/null +++ b/vault/wizards/vault_export_wizard.py @@ -0,0 +1,74 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging + +from odoo import api, fields, models +from odoo.tools import BinaryBytes, BinaryValue + +_logger = logging.getLogger(__name__) + + +class ExportWizard(models.TransientModel): + _name = "vault.export.wizard" + _description = "Export wizard for vaults" + + vault_id = fields.Many2one("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): + content = self._export_content( + rec.vault_id, + rec.entry_id, + rec.include_childs, + ) + rec.content = BinaryBytes(content.encode()) + + def _default_name(self): + return fields.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): + if isinstance(x, BinaryValue): + return x.to_base64() if x else False + 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..0d6e1e7a0e --- /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") + 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(self.env._("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..4baae1bfde --- /dev/null +++ b/vault/wizards/vault_send_wizard.py @@ -0,0 +1,54 @@ +# © 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", + 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() + + _value_check = models.Constraint( + "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(self.env._("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": self.env._("Created by %s", self.env.user.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..a6c6f58a56 --- /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 +from odoo.exceptions import UserError + +_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", required=True) + entry_id = fields.Many2one( + "vault.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() + if self.model not in ("vault.field", "vault.file"): + raise UserError(self.env._("Invalid model to store the secret")) + + self.env[self.model].create( + { + "entry_id": self.entry_id.id, + "name": self.name, + "iv": self.iv, + "value": self.secret, + } + ) 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 + +
+ + + + + + + + + + + + + +
+
+
+