Initial commit: OCA Server Auth packages (29 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:06 +02:00
commit 3ed80311c4
1325 changed files with 127292 additions and 0 deletions

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t
t-name="vault.FieldShareVault"
t-inherit="vault.FieldVault"
t-inherit-mode="primary"
owl="1"
>
<xpath expr="//div[@t-elif='props.readonly']" position="attributes">
<attribute name="t-elif">!isNew</attribute>
</xpath>
</t>
<t
t-name="vault.FileShareVault"
t-inherit="web.BinaryField"
t-inherit-mode="primary"
owl="1"
>
<xpath expr="//t[@t-if='!props.readonly']" position="attributes">
<attribute name="t-if">isNew</attribute>
</xpath>
</t>
<t t-name="vault.FieldPinVault" owl="1">
<div class="o_vault o_vault_error" t-if="!supported()">
<span>*******</span>
</div>
<div class="o_vault" t-else="">
<t t-call="vault.Field.buttons" />
<span t-esc="formattedValue" t-ref="span" />
</div>
</t>
<t t-inherit="vault.FieldVault" t-inherit-mode="extension" owl="1">
<xpath expr="//span[hasclass('o_vault_buttons')]" position="inside">
<button
t-if="shareButton"
class="btn btn-secondary btn-sm fa fa-external-link o_vault_share"
title="Share the secret with an external user"
aria-label="Share the secret with an external user"
t-on-click="_onShareValue"
/>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,49 @@
/** @odoo-module **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import VaultField from "vault.field";
import {_lt} from "@web/core/l10n/translation";
import {patch} from "@web/core/utils/patch";
import sh_utils from "vault.share.utils";
import utils from "vault.utils";
import vault from "vault";
// Extend the widget to share
patch(VaultField.prototype, "vault_share", {
get shareButton() {
return this.props.value;
},
/**
* Share the value for an external user
*
* @private
*/
async _onShareValue(ev) {
ev.stopPropagation();
const iv = await utils.generate_iv_base64();
const pin = sh_utils.generate_pin(sh_utils.PinSize);
const salt = utils.generate_bytes(utils.SaltLength).buffer;
const key = await utils.derive_key(pin, salt, utils.Derive.iterations);
const public_key = await vault.get_public_key();
const value = await this._decrypt(this.props.value);
this.action.doAction({
type: "ir.actions.act_window",
title: _lt("Share the secret"),
target: "new",
res_model: "vault.share",
views: [[false, "form"]],
context: {
default_secret: await utils.sym_encrypt(key, value, iv),
default_pin: await utils.asym_encrypt(
public_key,
pin + utils.generate_iv_base64()
),
default_iterations: utils.Derive.iterations,
default_iv: iv,
default_salt: utils.toBase64(salt),
},
});
},
});

View file

@ -0,0 +1,110 @@
/** @odoo-module **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import {Component, useRef, useState} from "@odoo/owl";
import VaultMixin from "vault.mixin";
import {_lt} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
import sh_utils from "vault.share.utils";
import {useService} from "@web/core/utils/hooks";
import utils from "vault.utils";
import vault from "vault";
export default class VaultPinField extends VaultMixin(Component) {
setup() {
super.setup();
this.action = useService("action");
this.span = useRef("span");
this.state = useState({
decrypted: false,
decryptedValue: "",
});
this.context = this.env.searchModel.context;
this.props.readonly = true;
}
get sendButton() {
return false;
}
get generateButton() {
return false;
}
get saveButton() {
return false;
}
/**
* Get the decrypted value or a placeholder
*
* @returns the decrypted value or a placeholder
*/
get formattedValue() {
if (!this.props.value) return "";
if (this.state.decrypted) return this.state.decryptedValue || "*******";
return "*******";
}
/**
* Decrypt the value using the private key of the vault and slice it to
* the actual pin size because there is a salt following
*
* @private
* @param {String} data
* @returns the decrypted data
*/
async _decrypt(data) {
if (!data) return data;
const pin_size = this.context.pin_size || sh_utils.PinSize;
const private_key = await vault.get_private_key();
const plain = await utils.asym_decrypt(private_key, data);
return plain.slice(0, pin_size);
}
/**
* Copy the decrypted secret to the clipboard
*
* @param {Object} ev
*/
async _onCopyValue(ev) {
ev.stopPropagation();
const value = await this._decrypt(this.props.value);
await navigator.clipboard.writeText(value);
}
/**
* Toggle between visible and invisible secret
*
* @param {Object} ev
*/
async _onShowValue(ev) {
ev.stopPropagation();
this.state.decrypted = !this.state.decrypted;
if (this.state.decrypted) {
this.state.decryptedValue = await this._decrypt(this.props.value);
} else {
this.state.decryptedValue = "";
}
await this.showValue();
}
/**
* Update the value shown
*/
async showValue() {
this.span.el.innerHTML = this.formattedValue;
}
}
VaultPinField.displayName = _lt("Vault Pin Field");
VaultPinField.supportedTypes = ["char"];
VaultPinField.template = "vault.FieldPinVault";
registry.category("fields").add("vault_pin", VaultPinField);

View file

@ -0,0 +1,15 @@
/** @odoo-module alias=vault.share.field **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import VaultField from "vault.field";
import VaultShareMixin from "vault.share.mixin";
import {_lt} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
export default class VaultShareField extends VaultShareMixin(VaultField) {}
VaultShareField.displayName = _lt("Vault Share Field");
VaultShareField.template = "vault.FieldShareVault";
registry.category("fields").add("vault_share_field", VaultShareField);

View file

@ -0,0 +1,15 @@
/** @odoo-module alias=vault.share.file **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import VaultFile from "vault.file";
import VaultShareMixin from "vault.share.mixin";
import {_lt} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
export default class VaultShareFile extends VaultShareMixin(VaultFile) {}
VaultShareFile.displayName = _lt("Vault Share Field");
VaultShareFile.template = "vault.FileShareVault";
registry.category("fields").add("vault_share_file", VaultShareFile);

View file

@ -0,0 +1,164 @@
/** @odoo-module alias=vault.share.mixin **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import sh_utils from "vault.share.utils";
import utils from "vault.utils";
import vault from "vault";
export default (x) => {
class Extended extends x {
setup() {
super.setup();
this.context = this.env.searchModel.context;
}
get shareButton() {
return false;
}
get sendButton() {
return false;
}
get isNew() {
return this.props.record.isNew;
}
/**
* Encrypt the pin with a random salt to make it hard to guess him by
* encrypting every possilbilty with the public key. Store the pin in the
* proper field
*
* @private
* @param {String} pin
*/
async _storePin(pin) {
const salt = utils.generate_iv_base64();
const crypted_pin = await utils.asym_encrypt(
await vault.get_public_key(),
pin + salt
);
await this._setFieldValue(this.props.fieldPin, crypted_pin);
}
/**
* Get the iterations
*
* @returns iterations for the password derivation
*/
async _getIterations() {
const record = this.props.record;
if (!record) return utils.Derive.iterations;
const iterations = record.data.iterations || utils.Derive.iterations;
await this._setFieldValue(this.props.fieldIterations, iterations);
return iterations;
}
/**
* Returns the pin from the class, record data, or generate a new pin if
* none s currently available
*
* @private
* @returns the pin
*/
async _getPin() {
const record = this.props.record;
if (!record) return null;
const pin_size = this.context.pin_size || sh_utils.PinSize;
let pin = record.data[this.props.fieldPin];
if (pin) {
// Decrypt the pin and slice him to the configured pin size
const private_key = await vault.get_private_key();
const plain = await utils.asym_decrypt(private_key, pin);
if (!plain) return null;
pin = plain.slice(0, pin_size);
return pin;
}
// Generate a new pin and store it
pin = sh_utils.generate_pin(pin_size);
await this._storePin(pin);
return pin;
}
/**
* Returns the salt from the class, record data, or generate a new salt if
* none is currently available
*
* @private
* @returns the salt
*/
async _getSalt() {
const record = this.props.record;
if (!record) return null;
let salt = record.data[this.props.fieldSalt];
if (salt) return salt;
// Generate a new salt and store him
salt = utils.toBase64(utils.generate_bytes(utils.SaltLength).buffer);
await this._setFieldValue(this.props.fieldSalt, salt);
return salt;
}
/**
* Decrypt the encrypted data using the pin, IV and salt
*
* @private
* @param {String} crypted
* @returns the decrypted secret
*/
async _decrypt(crypted) {
if (!utils.supported()) return null;
if (crypted === false) return false;
if (!this.props.value) return this.props.value;
const iv = await this._getIV();
const pin = await this._getPin();
const salt = utils.fromBase64(await this._getSalt());
const iterations = await this._getIterations();
const key = await utils.derive_key(pin, salt, iterations);
return await utils.sym_decrypt(key, crypted, iv);
}
/**
* Encrypt the data using the pin, IV and salt
*
* @private
* @param {String} data
* @returns the encrypted secret
*/
async _encrypt(data) {
if (!utils.supported()) return null;
const iv = await this._getIV();
const pin = await this._getPin();
const salt = utils.fromBase64(await this._getSalt());
const iterations = await this._getIterations();
const key = await utils.derive_key(pin, salt, iterations);
return await utils.sym_encrypt(key, data, iv);
}
}
Extended.defaultProps = {
...x.defaultProps,
fieldPin: "pin",
fieldSalt: "salt",
fieldIterations: "iterations",
};
Extended.props = {
...x.props,
fieldIterations: {type: String, optional: true},
fieldPin: {type: String, optional: true},
fieldSalt: {type: String, optional: true},
};
return Extended;
};

View file

@ -0,0 +1,3 @@
.o_vault_share {
white-space: pre-wrap;
}

View file

@ -0,0 +1,16 @@
/** @odoo-module alias=vault.share.utils **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import utils from "vault.utils";
const PinSize = 5;
function generate_pin(pin_size) {
return utils.generate_secret(pin_size, "0123456789");
}
export default {
PinSize: PinSize,
generate_pin: generate_pin,
};

View file

@ -0,0 +1,90 @@
/** @odoo-module **/
// © 2021-2024 Florian Kantelberg - initOS GmbH
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import utils from "vault.utils";
const data = {};
// Find the elements in the document and check if they have values
function find_elements() {
for (const id of [
"encrypted",
"salt",
"iv",
"encrypted_file",
"filename",
"iterations",
])
if (!data[id]) {
const element = document.getElementById(id);
data[id] = element && element.value;
}
}
function toggle_alert(element, successful) {
if (element) {
element.classList.add(successful ? "alert-success" : "alert-danger");
element.classList.remove(successful ? "alert-danger" : "alert-success");
}
}
function show(selector) {
const element = document.querySelector(selector);
if (element) element.classList.remove("o_hidden");
}
function hide(selector) {
const element = document.querySelector(selector);
if (element) element.classList.add("o_hidden");
}
document.getElementById("pin").onchange = async function () {
if (!utils.supported()) return;
find_elements();
// Derive the key from the pin
const key = await utils.derive_key(
this.value,
utils.fromBase64(data.salt),
data.iterations
);
hide("#secret_group");
hide("#file_group");
const pin = document.getElementById("pin");
const secret = document.getElementById("secret");
const secret_file = document.getElementById("secret_file");
if (!secret && !secret_file) return;
// There is no secret to decrypt
if (!this.value) {
toggle_alert(pin, false);
return;
}
// Decrypt the data and show the value
if (data.encrypted) {
secret.value = await utils.sym_decrypt(key, data.encrypted, data.iv);
toggle_alert(pin, secret.value);
show("#secret_group");
}
if (data.encrypted_file) {
const content = atob(
await utils.sym_decrypt(key, data.encrypted_file, data.iv)
);
const buffer = new ArrayBuffer(content.length);
const arr = new Uint8Array(buffer);
for (let i = 0; i < content.length; i++) arr[i] = content.charCodeAt(i);
const file = new Blob([arr]);
secret_file.text = data.filename;
secret_file.setAttribute("href", window.URL.createObjectURL(file));
secret_file.setAttribute("download", data.filename);
toggle_alert(pin, content);
show("#file_group");
}
};