From 76c1ff1f340b0135f7e8c917cf253d7ec730dea5 Mon Sep 17 00:00:00 2001 From: fkantelberg Date: Fri, 12 Mar 2021 14:02:30 +0100 Subject: [PATCH 01/21] Add vault and vault_share module Fix website in manifest Run pre-commit Move models to own files. Apply OCA conventions Add application icon --- vault_share/__init__.py | 4 + vault_share/__manifest__.py | 24 ++ vault_share/controllers/__init__.py | 4 + vault_share/controllers/main.py | 34 ++ vault_share/data/ir_cron.xml | 13 + vault_share/models/__init__.py | 4 + vault_share/models/vault_share.py | 72 ++++ vault_share/readme/CONTRIBUTORS.rst | 1 + vault_share/readme/DESCRIPTION.rst | 6 + vault_share/security/ir.model.access.csv | 2 + vault_share/security/ir_rule.xml | 12 + vault_share/static/description/icon.png | Bin 0 -> 2287 bytes vault_share/static/src/js/vault_fields.js | 55 +++ vault_share/static/src/js/vault_share.js | 56 +++ .../static/src/js/vault_share_widget.js | 355 ++++++++++++++++++ vault_share/static/src/js/vault_utils.js | 19 + vault_share/static/src/scss/vault_share.scss | 3 + vault_share/static/src/xml/templates.xml | 48 +++ vault_share/tests/__init__.py | 4 + vault_share/tests/test_share.py | 44 +++ vault_share/views/assets.xml | 32 ++ vault_share/views/menuitems.xml | 16 + vault_share/views/templates.xml | 44 +++ vault_share/views/vault_share_views.xml | 39 ++ 24 files changed, 891 insertions(+) create mode 100644 vault_share/__init__.py create mode 100644 vault_share/__manifest__.py create mode 100644 vault_share/controllers/__init__.py create mode 100644 vault_share/controllers/main.py create mode 100644 vault_share/data/ir_cron.xml create mode 100644 vault_share/models/__init__.py create mode 100644 vault_share/models/vault_share.py create mode 100644 vault_share/readme/CONTRIBUTORS.rst create mode 100644 vault_share/readme/DESCRIPTION.rst create mode 100644 vault_share/security/ir.model.access.csv create mode 100644 vault_share/security/ir_rule.xml create mode 100644 vault_share/static/description/icon.png create mode 100644 vault_share/static/src/js/vault_fields.js create mode 100644 vault_share/static/src/js/vault_share.js create mode 100644 vault_share/static/src/js/vault_share_widget.js create mode 100644 vault_share/static/src/js/vault_utils.js create mode 100644 vault_share/static/src/scss/vault_share.scss create mode 100644 vault_share/static/src/xml/templates.xml create mode 100644 vault_share/tests/__init__.py create mode 100644 vault_share/tests/test_share.py create mode 100644 vault_share/views/assets.xml create mode 100644 vault_share/views/menuitems.xml create mode 100644 vault_share/views/templates.xml create mode 100644 vault_share/views/vault_share_views.xml diff --git a/vault_share/__init__.py b/vault_share/__init__.py new file mode 100644 index 0000000000..05ae53c8cb --- /dev/null +++ b/vault_share/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import controllers, models diff --git a/vault_share/__manifest__.py b/vault_share/__manifest__.py new file mode 100644 index 0000000000..cdbf549b35 --- /dev/null +++ b/vault_share/__manifest__.py @@ -0,0 +1,24 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Vault - Share", + "summary": "Implementation of a mechanism to share secrets", + "license": "AGPL-3", + "version": "14.0.1.0.0", + "website": "https://github.com/OCA/server-auth", + "application": True, + "author": "initOS GmbH, Odoo Community Association (OCA)", + "category": "Vault", + "depends": ["vault"], + "data": [ + "data/ir_cron.xml", + "security/ir.model.access.csv", + "security/ir_rule.xml", + "views/assets.xml", + "views/menuitems.xml", + "views/templates.xml", + "views/vault_share_views.xml", + ], + "qweb": ["static/src/xml/templates.xml"], +} diff --git a/vault_share/controllers/__init__.py b/vault_share/controllers/__init__.py new file mode 100644 index 0000000000..aabfa83edd --- /dev/null +++ b/vault_share/controllers/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import main diff --git a/vault_share/controllers/main.py b/vault_share/controllers/main.py new file mode 100644 index 0000000000..4d908f0669 --- /dev/null +++ b/vault_share/controllers/main.py @@ -0,0 +1,34 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, http +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class Controller(http.Controller): + @http.route("/vault/share/", type="http", auth="public") + def vault_share(self, token): + ctx = {"disable_footer": True, "token": token} + secret = request.env["vault.share"].sudo().get(token) + if secret is None: + ctx["error"] = _("The secret expired") + return request.render("vault_share.share", ctx) + + if len(secret) != 1: + ctx["error"] = _("Invalid token") + return request.render("vault_share.share", ctx) + + ctx.update( + { + "encrypted": secret.secret, + "salt": secret.salt, + "iv": secret.iv, + "encrypted_file": secret.secret_file, + "filename": secret.filename, + } + ) + return request.render("vault_share.share", ctx) diff --git a/vault_share/data/ir_cron.xml b/vault_share/data/ir_cron.xml new file mode 100644 index 0000000000..362e5c1905 --- /dev/null +++ b/vault_share/data/ir_cron.xml @@ -0,0 +1,13 @@ + + + + Clean outgoing share + + code + model.clean() + 1 + minutes + -1 + + + diff --git a/vault_share/models/__init__.py b/vault_share/models/__init__.py new file mode 100644 index 0000000000..51a08ee32d --- /dev/null +++ b/vault_share/models/__init__.py @@ -0,0 +1,4 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import vault_share diff --git a/vault_share/models/vault_share.py b/vault_share/models/vault_share.py new file mode 100644 index 0000000000..24da5c41a9 --- /dev/null +++ b/vault_share/models/vault_share.py @@ -0,0 +1,72 @@ +# © 2021 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, timedelta +from uuid import uuid4 + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + + +class VaultShare(models.Model): + _name = "vault.share" + _description = _("Vault share outgoing secrets") + + user_id = fields.Many2one("res.users", default=lambda self: self.env.uid) + name = fields.Char(required=True) + share_link = fields.Char( + "Share URL", + compute="_compute_url", + store=False, + help="Using this link and pin people can access the secret.", + ) + token = fields.Char(readonly=True, required=True, default=lambda self: uuid4()) + secret = fields.Char() + secret_file = fields.Char() + filename = fields.Char() + salt = fields.Char(required=True) + iv = fields.Char(required=True) + pin = fields.Char(required=True, help="The pin needed to decrypt the share.") + accesses = fields.Integer( + "Access counter", + default=5, + help="Specifies how often a share can be accessed before deletion.", + ) + expiration = fields.Datetime( + default=lambda self: datetime.now() + timedelta(days=7), + help="Specifies how long a share can be accessed until deletion.", + ) + + _sql_constraints = [ + ( + "value_check", + "CHECK(secret IS NOT NULL OR secret_file IS NOT NULL)", + _("No value found"), + ), + ] + + @api.depends("token") + def _compute_url(self): + base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url") + for rec in self: + rec.share_link = f"{base_url}/vault/share/{rec.token}" + + @api.model + def get(self, token): + rec = self.search([("token", "=", token)], limit=1) + if not rec: + return rec + + if datetime.now() < rec.expiration and rec.accesses > 0: + rec.accesses -= 1 + return rec + + rec.unlink() + return None + + @api.model + def clean(self): + now = datetime.now() + self.search([("expiration", "<=", now), ("accesses", "<=", 0)]).unlink() diff --git a/vault_share/readme/CONTRIBUTORS.rst b/vault_share/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..e202826121 --- /dev/null +++ b/vault_share/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Florian Kantelberg diff --git a/vault_share/readme/DESCRIPTION.rst b/vault_share/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..51e526ca52 --- /dev/null +++ b/vault_share/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module implements possibilities to share specific secrets with external users. This bases on the vault implementation and the generated RSA key pair. + +Share +===== + +This allows an user to share a secret with external users. A share can be generated from a vault entry or directly created by an user. The secret is symmetrically encrypted by a key derived from a pin. To grant access the user has to transmit the link and pin with the external. If either the access counter reaches 0 or the share expires it will be deleted automatically. Due to the usage of a numeric pin and the browser side decryption a share is vulnerable to brute-force attacks and shouldn't be used as a permanent storage for secrets. For long time uses the user should create an account and a vault should be used. diff --git a/vault_share/security/ir.model.access.csv b/vault_share/security/ir.model.access.csv new file mode 100644 index 0000000000..46bc795e07 --- /dev/null +++ b/vault_share/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_vault_share,access_vault_share,model_vault_share,base.group_user,1,1,1,1 diff --git a/vault_share/security/ir_rule.xml b/vault_share/security/ir_rule.xml new file mode 100644 index 0000000000..936aba6b94 --- /dev/null +++ b/vault_share/security/ir_rule.xml @@ -0,0 +1,12 @@ + + + + vault.share.access.owner + + [('user_id', '=', user.id)] + + + + + + diff --git a/vault_share/static/description/icon.png b/vault_share/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c9035d9b2247bbe8008a1601aafb0524a2c13d GIT binary patch literal 2287 zcmVu1JM?9g1fDd5-sLv%rO|#VvfNWiDip926JK= z(_)Um4v=awN51v&u-B{S_cL>B2{A3^n9NZ*ro|kCIkrNO7IOk{;;K@MIWos+2)CG{ zn3D|<)Am(_27=meiONxl(UW&{JLqy@yB4CN>1A>}uwyU7TX(UmyOXX=7Th9BYjfPV zd!2Xkvn*AFniO9TwVxF0ha4xY7x72h$qLkr>p~`Tf~oUnHnKUT>G@NqF~Zmh6!JOb zbY{z54juR?Pwp9Hpq=<@6e#DpJad-cT|CdfN)^qI80J{?k?kr%s0Uf10m6e2{@m6+q&y@*L;RpXAq9?@|o>_eW}c zC(u?-M%QNQImh14#1miR#CSqb0NVO^bo>a%$M(^eLe8;4)Rx_wUafi=KXjNQgT1sR z#6(?Lc8`3PZ;TDmU3HxLs+Ib>W^2YVBM;7~5Dub&{I4+rZ2xv4m|it9=rJ)&Xk%{ zt?V#T9)f8y$7cV*5A%U)l_z|y{DYsL{v*>%j>ibMz_lwUd3pNZly$kb5uO@-fNYcc zoS6NXlyi=~kBD*)zpb|IIlxe)#OLVm$<|mE z*Jq9`0>~hJ=49y3b+8D4y3Fs96Bd0(!lDc@FEA0gGj z##J2&S`(^Oo1IMt4A9g6364GUF&0-=aN2V8wXb8t&dPWQ`J1d-d0*FZ#?xm`g+jTQ z7#Q!$(v$059~8hT7Jc^`Vvejz6;HI{8%Zzc+*w#L%WP9Ghq=OnS!HVRH|F=Yfz&22 zr!YIi?bLv|!{xlI7|;N7LVZly%(2-$uqRPfN^+I+?{cvaUuBXq#}3MO>deWo`@m5q zA0J8ym?D?2OmoNYbM(0CU`~~j$YwLgX87O{CMVJY1VEj~)W{rLVo7?; z$uRuzQI1axrv?h8Yn=Ss+uV%nkn3cQy*`4CGRJ24p`%QGXqYXjAOg5~PEWnYsRdW{ zQ%yL1$a)BZ!5oRW+B1#r{RT~&GN76&ED#-xj6KWbV=02NdX3-z)xd#WPlOhnzMj4!39CinO%5n>=ArvYQxrp=t7j|sL0f_p8U zt=H!a?0A1e%cqvFa$r;c%EFedA3AfqxBha9VUA}7Pcf^$jq15d%LF?#LGF^nRow~n zgOrX-f0UZf9F-Q(!Lc^(kc}NY->GCdNBLIERW8huF9EKDQ<`P!>>2X9J&4*5Er-p> zIoV(>`bMZD=Onmpo&P1@o1P8E@h8~KCDE$4**Pb=gI8D1NdnEiIxA0>b8KUc1Eok4$&Yn*1tS3%SI4myaS#-@@;@=`UC-4I`<9f~^oJ%k}WdAuPu3G-b zQ~PTNJv&#-tX)gBD$R3iHk$bKX3%#wT6Me^ssEKlf@)<4$Qx_3k(?9mb5w_{Eqawp zg2ny6m2{8=B)oek@qumY=6dE%rTj>!Aa!(%N)DnI_9@Zi#b#r7uzkCYnuseM*rWL<181k zt!2lDs>K{>$NA5yXX8^Zeo^%8+Ch6)2X-c_4eAFQdJdusZz1U0gE4w4CJiJ_4;@?u zlA#y3ryJLk-vVMSHJsHW&R73&x`q`c}k@c7=tSF`bk~PYt5OE zlMow>8YlE<1Se-QPB3i0id|z8 + + + +
+ +
+
+ + + +
+
+
+ + +
+ + +
+
+ + + +