"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecureDelegationsManager = void 0;
const EntityShareRequest_1 = require("../../icc-api/model/requests/EntityShareRequest");
const lru_temporised_async_cache_1 = require("../utils/lru-temporised-async-cache");
const utils_1 = require("./utils");
const utils_2 = require("../utils");
const SecureDelegation_1 = require("../../icc-api/model/SecureDelegation");
const EntitySharedMetadataUpdateRequest_1 = require("../../icc-api/model/requests/EntitySharedMetadataUpdateRequest");
var EntryUpdateTypeEnum = EntitySharedMetadataUpdateRequest_1.EntitySharedMetadataUpdateRequest.EntryUpdateTypeEnum;
var AccessLevelEnum = SecureDelegation_1.SecureDelegation.AccessLevelEnum;
const SecurityMetadata_1 = require("../../icc-api/model/SecurityMetadata");
const RSA_1 = require("./RSA");
class SecureDelegationsManager {
    constructor(exchangeDataManager, exchangeDataMapManager, secureDelegationsEncryption, accessControlSecretUtils, userKeys, primitives, dataOwnerApi, cryptoStrategies, selfNeedsAnonymousDelegations) {
        this.exchangeDataManager = exchangeDataManager;
        this.exchangeDataMapManager = exchangeDataMapManager;
        this.secureDelegationsEncryption = secureDelegationsEncryption;
        this.accessControlSecretUtils = accessControlSecretUtils;
        this.userKeys = userKeys;
        this.primitives = primitives;
        this.dataOwnerApi = dataOwnerApi;
        this.cryptoStrategies = cryptoStrategies;
        this.selfNeedsAnonymousDelegations = selfNeedsAnonymousDelegations;
        this.dataOwnerInfoCache = new lru_temporised_async_cache_1.LruTemporisedAsyncCache(100, () => 30 * 60 * 1000);
    }
    /**
     * Note: this method does not save the updated entity.
     * @param entity an entity, must already have secret foreign keys initialised.
     * @param entityType the type of the entity
     * @param secretIds the initial secret ids to include and share with the auto-delegations
     * @param owningEntityIds the initial owning entity ids to include and share with the auto-delegations
     * @param encryptionKeys the initial encryption keys to include and share with the auto-delegations
     * @param autoDelegations the data owners which will initially have access to the entity in addition to the current data owner and the access level
     * they will have on the entity.
     * @param alternateRootDelegation by default a new entity is created with a root delegation from self to self. In keyless mode this is not possible,
     * and instead the root delegation will be from self to another. You have to specify which delegate will be part of the root delegation.
     * @return the entity with the security metadata initialised for the provided parameters.
     */
    entityWithInitialisedEncryptedMetadata(entity, entityType, secretIds, owningEntityIds, encryptionKeys, autoDelegations, alternateRootDelegation) {
        return __awaiter(this, void 0, void 0, function* () {
            const entityWithType = { entity, type: entityType };
            const selfId = yield this.dataOwnerApi.getCurrentDataOwnerId();
            const rootDelegationDelegate = alternateRootDelegation !== null && alternateRootDelegation !== void 0 ? alternateRootDelegation : selfId;
            const rootDelegationInfo = yield this.makeSecureDelegationInfo(entityWithType, rootDelegationDelegate, secretIds, encryptionKeys, owningEntityIds, AccessLevelEnum.WRITE, undefined);
            const otherDelegationsInfo = [];
            for (const [delegateId, permissions] of Object.entries(autoDelegations)) {
                if (delegateId !== rootDelegationDelegate) {
                    otherDelegationsInfo.push(yield this.makeSecureDelegationInfo(entityWithType, delegateId, secretIds, encryptionKeys, owningEntityIds, permissions, rootDelegationInfo.delegationKey));
                }
            }
            const secureDelegations = Object.fromEntries([rootDelegationInfo, ...otherDelegationsInfo].map(({ delegationKey, delegation }) => [delegationKey, delegation]));
            const delegationForExchangeDataMaps = selfId == rootDelegationDelegate ? otherDelegationsInfo : [rootDelegationInfo, ...otherDelegationsInfo];
            const newExchangeDataMaps = Object.fromEntries(delegationForExchangeDataMaps
                .filter(({ encryptedExchangeDataId }) => !!encryptedExchangeDataId)
                .map(({ accessControlKeyHex, encryptedExchangeDataId }) => [accessControlKeyHex, encryptedExchangeDataId]));
            yield this.exchangeDataMapManager.createExchangeDataMaps(newExchangeDataMaps);
            return Object.assign(Object.assign({}, entity), { securityMetadata: new SecurityMetadata_1.SecurityMetadata({ secureDelegations }) });
        });
    }
    /**
     * Make a request for sharing an entity with a delegate or to update existing shared metadata by adding additional secretIds, encryptionKeys or
     * owningEntityIds if there is already some metadata shared between the current data owner and the delegate. In case there is already a delegation
     * for the delegate, and it already contains all the provided metadata, this method returns undefined, since there is no need to make any request to
     * share the provided data.
     * @param entityWithType an entity with type
     * @param delegateId the id of the delegate
     * @param shareSecretIds the secret ids to share
     * @param shareEncryptionKeys the encryption keys to share
     * @param shareOwningEntityIds the owning entity ids to share
     * @param newDelegationPermissions the permissions to grant to the delegate in case a new delegation needs to be created. If this method creates an
     * update request instead of a share request, this parameter is ignored.
     * @return the request to share the entity, or the request to update the shared metadata for the entity, or undefined if there is no need to update
     * the entity to allow the delegate to access the provided data.
     */
    makeShareOrUpdateRequestParams(entityWithType, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds, newDelegationPermissions) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const exchangeDataInfo = yield this.exchangeDataManager.getOrCreateEncryptionDataTo(delegateId);
            const secureDelegationKey = yield this.accessControlSecretUtils.secureDelegationKeyFor(exchangeDataInfo.accessControlSecret, entityWithType.type);
            const existingSecureDelegation = (_b = (_a = entityWithType.entity.securityMetadata) === null || _a === void 0 ? void 0 : _a.secureDelegations) === null || _b === void 0 ? void 0 : _b[secureDelegationKey];
            if (existingSecureDelegation) {
                const updateParams = yield this.makeUpdateRequestParams(secureDelegationKey, existingSecureDelegation, exchangeDataInfo, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds);
                return updateParams ? { update: updateParams } : undefined;
            }
            else {
                const accessControlKeys = (0, utils_2.ua2hex)(yield this.accessControlSecretUtils.accessControlKeyFor(exchangeDataInfo.accessControlSecret, entityWithType.type));
                return {
                    share: yield this.makeShareRequestParams(exchangeDataInfo, accessControlKeys, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds, newDelegationPermissions),
                };
            }
        });
    }
    makeUpdateRequestParams(canonicalKey, existingDelegation, exchangeDataInfo, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds) {
        return __awaiter(this, void 0, void 0, function* () {
            const existingSecretIds = new Set(yield this.secureDelegationsEncryption.decryptSecretIds(existingDelegation, exchangeDataInfo.exchangeKey));
            const existingEncryptionKeys = new Set(yield this.secureDelegationsEncryption.decryptEncryptionKeys(existingDelegation, exchangeDataInfo.exchangeKey));
            const existingOwningEntityIds = new Set(yield this.secureDelegationsEncryption.decryptOwningEntityIds(existingDelegation, exchangeDataInfo.exchangeKey));
            const newSecretIds = shareSecretIds.filter((id) => !existingSecretIds.has(id));
            const newEncryptionKeys = shareEncryptionKeys.filter((key) => !existingEncryptionKeys.has(key));
            const newOwningEntityIds = shareOwningEntityIds.filter((id) => !existingOwningEntityIds.has(id));
            if (newSecretIds.length || newEncryptionKeys.length || newOwningEntityIds.length) {
                const encryptedNewSecretIds = yield this.secureDelegationsEncryption.encryptSecretIds(newSecretIds, exchangeDataInfo.exchangeKey);
                const encryptedNewEncryptionKeys = yield this.secureDelegationsEncryption.encryptEncryptionKeys(newEncryptionKeys, exchangeDataInfo.exchangeKey);
                const encryptedNewOwningEntityIds = yield this.secureDelegationsEncryption.encryptOwningEntityIds(newOwningEntityIds, exchangeDataInfo.exchangeKey);
                return new EntitySharedMetadataUpdateRequest_1.EntitySharedMetadataUpdateRequest({
                    metadataAccessControlHash: canonicalKey,
                    secretIds: Object.fromEntries(encryptedNewSecretIds.map((id) => [id, EntryUpdateTypeEnum.CREATE])),
                    encryptionKeys: Object.fromEntries(encryptedNewEncryptionKeys.map((key) => [key, EntryUpdateTypeEnum.CREATE])),
                    owningEntityIds: Object.fromEntries(encryptedNewOwningEntityIds.map((id) => [id, EntryUpdateTypeEnum.CREATE])),
                });
            }
            else
                return undefined;
        });
    }
    makeShareRequestParams(exchangeDataInfo, accessControlKey, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds, newDelegationPermissions) {
        return __awaiter(this, void 0, void 0, function* () {
            return new EntityShareRequest_1.EntityShareRequest(Object.assign(Object.assign({}, (yield this.makeSecureDelegationEncryptedData(exchangeDataInfo, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds))), { requestedPermissions: newDelegationPermissions, accessControlKeys: [accessControlKey] }));
        });
    }
    makeSecureDelegationInfo(entity, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds, permissions, parentDelegationKey) {
        return __awaiter(this, void 0, void 0, function* () {
            // Be wary of explicit delegator and explicit delegate
            const exchangeDataInfo = yield this.exchangeDataManager.getOrCreateEncryptionDataTo(delegateId);
            const accessControlHash = yield this.accessControlSecretUtils.secureDelegationKeyFor(exchangeDataInfo.accessControlSecret, entity.type);
            const accessControlKey = (0, utils_2.ua2hex)(yield this.accessControlSecretUtils.accessControlKeyFor(exchangeDataInfo.accessControlSecret, entity.type));
            const encryptedDelegationInfo = yield this.makeSecureDelegationEncryptedData(exchangeDataInfo, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds);
            const delegation = new SecureDelegation_1.SecureDelegation({
                delegator: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.explicitDelegator,
                delegate: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.explicitDelegate,
                secretIds: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.secretIds,
                encryptionKeys: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.encryptionKeys,
                owningEntityIds: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.owningEntityIds,
                parentDelegations: parentDelegationKey ? [parentDelegationKey] : undefined,
                exchangeDataId: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.exchangeDataId,
                encryptedExchangeDataId: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.encryptedExchangeDataId,
                permissions: permissions,
            });
            return {
                delegationKey: accessControlHash,
                accessControlKeyHex: accessControlKey,
                delegation,
                encryptedExchangeDataId: encryptedDelegationInfo === null || encryptedDelegationInfo === void 0 ? void 0 : encryptedDelegationInfo.encryptedExchangeDataId,
            };
        });
    }
    makeSecureDelegationEncryptedData(exchangeDataInfo, delegateId, shareSecretIds, shareEncryptionKeys, shareOwningEntityIds) {
        return __awaiter(this, void 0, void 0, function* () {
            const selfId = yield this.dataOwnerApi.getCurrentDataOwnerId();
            const exchangeDataIdInfo = delegateId === selfId
                ? this.makeExchangeDataIdInfoForSelf(selfId, exchangeDataInfo.exchangeData)
                : yield this.makeExchangeDataIdInfoForDelegate(selfId, delegateId, exchangeDataInfo.exchangeData);
            const encryptedSecretIds = yield this.secureDelegationsEncryption.encryptSecretIds(shareSecretIds, exchangeDataInfo.exchangeKey);
            const encryptedEncryptionKeys = yield this.secureDelegationsEncryption.encryptEncryptionKeys(shareEncryptionKeys, exchangeDataInfo.exchangeKey);
            const encryptedOwningEntityIds = yield this.secureDelegationsEncryption.encryptOwningEntityIds(shareOwningEntityIds, exchangeDataInfo.exchangeKey);
            return Object.assign(Object.assign({}, exchangeDataIdInfo), { secretIds: encryptedSecretIds, encryptionKeys: encryptedEncryptionKeys, owningEntityIds: encryptedOwningEntityIds });
        });
    }
    makeExchangeDataIdInfoForDelegate(selfId, delegateId, exchangeData) {
        return __awaiter(this, void 0, void 0, function* () {
            const delegateInfo = yield this.getDataOwnerInfo(delegateId);
            if (!delegateInfo.requiresAnonymousDelegations && !this.selfNeedsAnonymousDelegations) {
                return {
                    explicitDelegator: selfId,
                    explicitDelegate: delegateId,
                    exchangeDataId: exchangeData.id,
                };
            }
            else if (delegateInfo.requiresAnonymousDelegations && !this.selfNeedsAnonymousDelegations) {
                return {
                    explicitDelegator: selfId,
                    encryptedExchangeDataId: yield this.secureDelegationsEncryption.encryptExchangeDataId(exchangeData.id, Object.fromEntries(this.userKeys.getSelfVerifiedKeys().map((keyInfo) => [(0, utils_1.fingerprintV1toV2)(keyInfo.fingerprint), keyInfo.pair.publicKey]))),
                };
            }
            else if (!delegateInfo.requiresAnonymousDelegations && this.selfNeedsAnonymousDelegations) {
                /**
                 * Important: this requires that the exchange data signature also validates the authenticity of the public keys included in there.
                 */
                const fingerprintsOfVerifiedExchangeData = new Set(Object.keys(exchangeData.exchangeKey));
                const delegateVerifiedKeys = {};
                for (const keyHex of [...delegateInfo.availablePublicKeysHexWithSha1, ...delegateInfo.availablePublicKeysHexWithSha256]) {
                    const currFp = (0, utils_1.fingerprintV2)(keyHex);
                    const shaVersion = delegateInfo.availablePublicKeysHexWithSha1.includes(keyHex) ? RSA_1.ShaVersion.Sha1 : RSA_1.ShaVersion.Sha256;
                    if (fingerprintsOfVerifiedExchangeData.has(currFp)) {
                        delegateVerifiedKeys[currFp] = yield this.primitives.RSA.importKey('spki', (0, utils_2.ua2ab)((0, utils_2.hex2ua)(keyHex)), ['encrypt'], shaVersion);
                    }
                }
                if (!Object.keys(delegateVerifiedKeys).length)
                    throw new Error('Illegal state: could not find any verified key for delegate in verified exchange data.');
                return {
                    explicitDelegate: delegateId,
                    encryptedExchangeDataId: yield this.secureDelegationsEncryption.encryptExchangeDataId(exchangeData.id, delegateVerifiedKeys),
                };
            }
            else
                return {};
        });
    }
    makeExchangeDataIdInfoForSelf(selfId, exchangeData) {
        if (this.selfNeedsAnonymousDelegations) {
            return {};
        }
        else {
            return {
                exchangeDataId: exchangeData.id,
                explicitDelegator: selfId,
                explicitDelegate: selfId,
            };
        }
    }
    getDataOwnerInfo(dataOwnerId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.dataOwnerInfoCache.get(dataOwnerId, () => __awaiter(this, void 0, void 0, function* () {
                const dataOwnerWithType = yield this.dataOwnerApi.getCryptoActorStub(dataOwnerId);
                return {
                    item: {
                        requiresAnonymousDelegations: this.cryptoStrategies.dataOwnerRequiresAnonymousDelegation(dataOwnerWithType),
                        availablePublicKeysHexWithSha1: Array.from((0, utils_1.hexPublicKeysWithSha1Of)(dataOwnerWithType.stub)),
                        availablePublicKeysHexWithSha256: Array.from((0, utils_1.hexPublicKeysWithSha256Of)(dataOwnerWithType.stub)),
                    },
                };
            }));
        });
    }
}
exports.SecureDelegationsManager = SecureDelegationsManager;
//# sourceMappingURL=SecureDelegationsManager.js.map