mirror of
https://github.com/bringout/oca-server-auth.git
synced 2026-04-19 07:32:05 +02:00
Initial commit: OCA Server Auth packages (29 packages)
This commit is contained in:
commit
3ed80311c4
1325 changed files with 127292 additions and 0 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# © 2021 Florian Kantelberg - initOS GmbH
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExportWizard(models.TransientModel):
|
||||
_name = "vault.export.wizard"
|
||||
_description = _("Export wizard for vaults")
|
||||
|
||||
vault_id = fields.Many2one("vault", "Vault")
|
||||
entry_id = fields.Many2one(
|
||||
"vault.entry", "Entries", domain="[('vault_id', '=', vault_id)]"
|
||||
)
|
||||
master_key = fields.Char(related="vault_id.master_key")
|
||||
name = fields.Char(default=lambda self: self._default_name())
|
||||
content = fields.Binary("Download", attachment=False)
|
||||
include_childs = fields.Boolean(default=True)
|
||||
|
||||
@api.onchange("vault_id", "entry_id")
|
||||
def _onchange_content(self):
|
||||
for rec in self.with_context(skip_log=True):
|
||||
rec.content = self._export_content(
|
||||
rec.vault_id,
|
||||
rec.entry_id,
|
||||
rec.include_childs,
|
||||
)
|
||||
|
||||
def _default_name(self):
|
||||
return datetime.now().strftime("database-%Y%m%d-%H%M.json")
|
||||
|
||||
def _export_content(self, vault=None, entry=None, include_childs=False):
|
||||
if entry:
|
||||
entries = entry
|
||||
elif vault:
|
||||
entries = vault.entry_ids.filtered_domain([("parent_id", "=", False)])
|
||||
else:
|
||||
return json.dumps([])
|
||||
|
||||
data = [self._export_entry(x, include_childs) for x in entries]
|
||||
return json.dumps(data)
|
||||
|
||||
@api.model
|
||||
def _export_field(self, rec):
|
||||
def ensure_string(x):
|
||||
return x.decode() if isinstance(x, bytes) else x
|
||||
|
||||
return {f: ensure_string(rec[f]) for f in ["name", "iv", "value"]}
|
||||
|
||||
@api.model
|
||||
def _export_entry(self, entry, include_childs=False):
|
||||
if include_childs:
|
||||
childs = [self._export_entry(x, include_childs) for x in entry.child_ids]
|
||||
else:
|
||||
childs = []
|
||||
|
||||
return {
|
||||
"uuid": entry.uuid,
|
||||
"name": entry.name,
|
||||
"note": entry.note,
|
||||
"url": entry.url,
|
||||
"fields": entry.field_ids.mapped(self._export_field),
|
||||
"files": entry.file_ids.mapped(self._export_field),
|
||||
"childs": childs,
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_vault_export_wizard" model="ir.ui.view">
|
||||
<field name="model">vault.export.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="vault_id" invisible="1" />
|
||||
<field name="entry_id" invisible="1" />
|
||||
<field name="master_key" invisible="1" />
|
||||
<field name="name" invisible="1" />
|
||||
|
||||
<group>
|
||||
<field
|
||||
name="content"
|
||||
widget="vault_export_file"
|
||||
filename="name"
|
||||
readonly="1"
|
||||
/>
|
||||
<field name="include_childs" />
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button type="special" string="Close" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# © 2021 Florian Kantelberg - initOS GmbH
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import json
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImportWizardPath(models.TransientModel):
|
||||
_name = "vault.import.wizard.path"
|
||||
_description = _("Import wizard path for vaults")
|
||||
|
||||
name = fields.Char(required=True)
|
||||
uuid = fields.Char(required=True)
|
||||
|
||||
|
||||
class ImportWizard(models.TransientModel):
|
||||
_name = "vault.import.wizard"
|
||||
_description = _("Import wizard for vaults")
|
||||
|
||||
vault_id = fields.Many2one("vault", "Vault")
|
||||
parent_id = fields.Many2one(
|
||||
"vault.entry",
|
||||
"Parent Entry",
|
||||
domain="[('vault_id', '=', vault_id)]",
|
||||
)
|
||||
master_key = fields.Char(related="vault_id.master_key")
|
||||
name = fields.Char()
|
||||
content = fields.Binary("Database", attachment=False)
|
||||
crypted_content = fields.Char()
|
||||
uuid = fields.Char(default=lambda self: uuid4())
|
||||
|
||||
path = fields.Many2one(
|
||||
"vault.import.wizard.path",
|
||||
"Path to import",
|
||||
default="",
|
||||
domain="[('uuid', '=', uuid)]",
|
||||
)
|
||||
|
||||
@api.onchange("crypted_content", "content")
|
||||
def _onchange_content(self):
|
||||
for rec in self:
|
||||
if rec.crypted_content:
|
||||
for entry in json.loads(rec.crypted_content or []):
|
||||
rec._create_path(entry)
|
||||
|
||||
def _create_path(self, entry, path=None):
|
||||
self.ensure_one()
|
||||
p = f"{path} / {entry['name']}" if path else entry["name"]
|
||||
|
||||
if "name" in entry:
|
||||
self.env["vault.import.wizard.path"].create({"uuid": self.uuid, "name": p})
|
||||
|
||||
for child in entry.get("childs", []):
|
||||
self._create_path(child, p)
|
||||
|
||||
def _import_field(self, entry, model, data):
|
||||
if not data:
|
||||
return
|
||||
|
||||
# Only copy specific fields
|
||||
vals = {f: data[f] for f in ["name", "iv", "value"]}
|
||||
|
||||
# Update already existing records
|
||||
domain = [("entry_id", "=", entry.id), ("name", "=", data["name"])]
|
||||
rec = model.search(domain)
|
||||
if rec:
|
||||
rec.write(vals)
|
||||
else:
|
||||
rec.create({"entry_id": entry.id, **vals})
|
||||
|
||||
def _import_entry(self, entry, parent=None, path=None):
|
||||
p = f"{path} / {entry['name']}" if path else entry["name"]
|
||||
result = self.env["vault.entry"]
|
||||
if p.startswith(self.path.name or ""):
|
||||
if not parent:
|
||||
parent = self.env["vault.entry"]
|
||||
|
||||
# Update existing records if already imported
|
||||
rec = self.env["vault.entry"]
|
||||
if entry.get("uuid"):
|
||||
domain = [
|
||||
("uuid", "=", entry["uuid"]),
|
||||
("vault_id", "=", self.vault_id.id),
|
||||
]
|
||||
rec = rec.search(domain, limit=1)
|
||||
|
||||
# If record not found create a new one
|
||||
vals = {f: entry.get(f) for f in ["name", "note", "url", "uuid"]}
|
||||
if not rec:
|
||||
rec = rec.create(
|
||||
{"vault_id": self.vault_id.id, "parent_id": parent.id, **vals}
|
||||
)
|
||||
else:
|
||||
rec.write({"parent_id": parent.id, **vals})
|
||||
|
||||
# Create/update the entry fields
|
||||
for field in entry.get("fields", []):
|
||||
self._import_field(rec, self.env["vault.field"], field)
|
||||
|
||||
# Create/update the entry files
|
||||
for file in entry.get("files", []):
|
||||
self._import_field(rec, self.env["vault.file"], file)
|
||||
|
||||
result |= rec
|
||||
|
||||
else:
|
||||
rec = None
|
||||
|
||||
# Create the sub-entries
|
||||
for child in entry.get("childs", []):
|
||||
result |= self._import_entry(child, rec, p)
|
||||
|
||||
return result
|
||||
|
||||
def action_import(self):
|
||||
self.ensure_one()
|
||||
|
||||
try:
|
||||
data = json.loads(self.crypted_content)
|
||||
entries = self.env["vault.entry"]
|
||||
for entry in data:
|
||||
entries |= self.with_context(skip_log=True)._import_entry(
|
||||
entry, self.parent_id
|
||||
)
|
||||
|
||||
self.vault_id.log_entry(f"Imported entries from file {self.name}")
|
||||
except Exception as e:
|
||||
raise UserError(_("Invalid file to import from")) from e
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_vault_import_wizard" model="ir.ui.view">
|
||||
<field name="model">vault.import.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="master_key" invisible="1" />
|
||||
<field name="vault_id" invisible="1" />
|
||||
<field name="crypted_content" invisible="1" />
|
||||
<field name="name" invisible="1" />
|
||||
<field name="uuid" invisible="1" />
|
||||
|
||||
<div>The files must end on one of the supported file type:</div>
|
||||
<ul>
|
||||
<li>Custom JSON format <b>.json</b></li>
|
||||
<li>Keepass Database <b>.kdbx</b></li>
|
||||
</ul>
|
||||
|
||||
<group>
|
||||
<field name="content" filename="name" />
|
||||
<field
|
||||
name="parent_id"
|
||||
options="{'no_create_edit': True, 'no_open': True}"
|
||||
/>
|
||||
<field
|
||||
name="path"
|
||||
attrs="{'invisible': [('crypted_content', '=', False)]}"
|
||||
options="{'no_create_edit': True, 'no_open': True}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button
|
||||
type="object"
|
||||
name="action_import"
|
||||
string="Import"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': [('crypted_content', '=', False)]}"
|
||||
/>
|
||||
<button
|
||||
type="object"
|
||||
name="action_import"
|
||||
string="Import"
|
||||
attrs="{'invisible': [('crypted_content', '!=', False)]}"
|
||||
/>
|
||||
or
|
||||
<button type="special" string="Cancel" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# © 2021 Florian Kantelberg - initOS GmbH
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VaultSendWizard(models.TransientModel):
|
||||
_name = "vault.send.wizard"
|
||||
_description = _("Wizard to send another user a secret")
|
||||
|
||||
user_id = fields.Many2one(
|
||||
"res.users",
|
||||
"User",
|
||||
required=True,
|
||||
domain=[("keys", "!=", False), ("inbox_enabled", "=", True)],
|
||||
)
|
||||
name = fields.Char(required=True)
|
||||
public = fields.Char(related="user_id.active_key.public")
|
||||
iv = fields.Char(required=True)
|
||||
key_user = fields.Char(required=True)
|
||||
key = fields.Char(required=True)
|
||||
secret = fields.Char()
|
||||
secret_file = fields.Char()
|
||||
filename = fields.Char()
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"value_check",
|
||||
"CHECK(secret IS NOT NULL OR secret_file IS NOT NULL)",
|
||||
_("No value found"),
|
||||
),
|
||||
]
|
||||
|
||||
def action_send(self):
|
||||
if not self.secret and not self.secret_file:
|
||||
raise ValidationError(_("Neither a secret nor file was given"))
|
||||
|
||||
self.ensure_one()
|
||||
self.env["vault.inbox"].sudo().create(
|
||||
{
|
||||
"name": self.name,
|
||||
"accesses": 0,
|
||||
"secret": self.secret,
|
||||
"secret_file": self.secret_file,
|
||||
"iv": self.iv,
|
||||
"key": self.key_user,
|
||||
"user_id": self.user_id.id,
|
||||
"filename": self.filename,
|
||||
"log_ids": [(0, 0, {"name": _("Created by %s") % self.user_id.name})],
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_vault_send_wizard" model="ir.ui.view">
|
||||
<field name="model">vault.send.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div>
|
||||
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.
|
||||
</div>
|
||||
<group>
|
||||
<field name="iv" invisible="1" />
|
||||
<field name="key" invisible="1" />
|
||||
<field name="key_user" invisible="1" />
|
||||
<field name="secret" invisible="1" />
|
||||
<field name="public" invisible="1" />
|
||||
<field
|
||||
name="user_id"
|
||||
options="{'no_open': true, 'no_create_edit': true, 'no_quick_create': true}"
|
||||
/>
|
||||
<field name="name" />
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button
|
||||
type="object"
|
||||
name="action_send"
|
||||
string="Send"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
or
|
||||
<button type="special" string="Cancel" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# © 2021 Florian Kantelberg - initOS GmbH
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VaultStoreWizard(models.TransientModel):
|
||||
_name = "vault.store.wizard"
|
||||
_description = _("Wizard store a shared secret in a vault")
|
||||
|
||||
vault_id = fields.Many2one("vault", "Vault", required=True)
|
||||
entry_id = fields.Many2one(
|
||||
"vault.entry",
|
||||
"Entry",
|
||||
domain="[('vault_id', '=', vault_id)]",
|
||||
required=True,
|
||||
)
|
||||
model = fields.Char(required=True)
|
||||
master_key = fields.Char(compute="_compute_master_key", store=False)
|
||||
name = fields.Char(required=True)
|
||||
iv = fields.Char(required=True)
|
||||
key = fields.Char(required=True)
|
||||
secret = fields.Char(required=True)
|
||||
secret_temporary = fields.Char(required=True)
|
||||
|
||||
@api.depends("entry_id", "vault_id")
|
||||
def _compute_master_key(self):
|
||||
for rec in self:
|
||||
rec.master_key = rec.vault_id.master_key
|
||||
|
||||
def action_store(self):
|
||||
self.ensure_one()
|
||||
try:
|
||||
self.env[self.model].create(
|
||||
{
|
||||
"entry_id": self.entry_id.id,
|
||||
"name": self.name,
|
||||
"iv": self.iv,
|
||||
"value": self.secret,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_vault_store_wizard" model="ir.ui.view">
|
||||
<field name="model">vault.store.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="iv" invisible="1" />
|
||||
<field name="key" invisible="1" />
|
||||
<field name="master_key" invisible="1" />
|
||||
<field name="secret" invisible="1" />
|
||||
<field name="secret_temporary" invisible="1" />
|
||||
<field
|
||||
name="vault_id"
|
||||
options="{'no_create_edit': True, 'no_open': True, 'no_quick_create': true}"
|
||||
/>
|
||||
<field
|
||||
name="entry_id"
|
||||
attrs="{'invisible': [('vault_id', '=', False)]}"
|
||||
options="{'no_create_edit': True, 'no_open': True, 'no_quick_create': true}"
|
||||
/>
|
||||
<field name="name" />
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button
|
||||
type="object"
|
||||
name="action_store"
|
||||
string="Store"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
or
|
||||
<button type="special" string="Cancel" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue