"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.DelegationsDeAnonymization = void 0;
const SecureDelegation_1 = require("../../icc-api/model/SecureDelegation");
var AccessLevelEnum = SecureDelegation_1.SecureDelegation.AccessLevelEnum;
const SecureDelegationKeyMap_1 = require("../../icc-api/model/internal/SecureDelegationKeyMap");
const ShareMetadataBehaviour_1 = require("./ShareMetadataBehaviour");
const EntityShareRequest_1 = require("../../icc-api/model/requests/EntityShareRequest");
var RequestedPermissionEnum = EntityShareRequest_1.EntityShareRequest.RequestedPermissionEnum;
const utils_1 = require("../utils");
const IccSecureDelegationKeyMapApi_1 = require("../../icc-api/api/internal/IccSecureDelegationKeyMapApi");
const XHR_1 = require("../../icc-api/api/XHR");
const AuthenticationProvider_1 = require("../auth/AuthenticationProvider");
const AccessControlKeysHeadersProvider_1 = require("./AccessControlKeysHeadersProvider");
// TODO could be optimised using bulk methods
class DelegationsDeAnonymization {
    constructor(dataOwnerApi, securityMetadataDecryptor, xapis, cryptoPrimitives, accessControlSecretUtils, host, headers, authenticationProvider = new AuthenticationProvider_1.NoAuthenticationProvider(), fetchImpl = typeof window !== 'undefined'
        ? window.fetch
        : typeof self !== 'undefined'
            ? self.fetch
            : fetch, accessControlKeysHeadersProvider) {
        this.dataOwnerApi = dataOwnerApi;
        this.securityMetadataDecryptor = securityMetadataDecryptor;
        this.xapis = xapis;
        this.cryptoPrimitives = cryptoPrimitives;
        this.accessControlSecretUtils = accessControlSecretUtils;
        this.accessControlKeysHeadersProvider = accessControlKeysHeadersProvider;
        this.delegationKeyMapFieldsToEncrypt = (0, utils_1.parseEncryptedFields)(['delegate', 'delegator'], 'SecureDelegationKeyMap');
        this.delegationKeyMapApi = new IccSecureDelegationKeyMapApi_1.IccSecureDelegationKeyMapApi(host, headers, authenticationProvider, fetchImpl);
    }
    /**
     * Creates / updates information to allow the data owners in {@link shareWithDataOwners} to de-anonymize the delegations contained within
     * {@link entity}.
     * Note that the delegation de-anonymization information may be used also with other entities of the same type.
     */
    createOrUpdateDeAnonymizationInfo(entityWithType, shareWithDataOwners) {
        return __awaiter(this, void 0, void 0, function* () {
            const delegationsDetails = Object.entries(yield this.securityMetadataDecryptor.getDelegationMemberDetails(entityWithType)).flatMap(([canonicalKey, delegation]) => {
                var _a, _b;
                const aliases = Object.entries((_b = (_a = entityWithType.entity.securityMetadata) === null || _a === void 0 ? void 0 : _a.keysEquivalences) !== null && _b !== void 0 ? _b : {}).flatMap(([alias, canon]) => {
                    if (canon == canonicalKey) {
                        return [alias];
                    }
                    else {
                        return [];
                    }
                });
                return [[canonicalKey, delegation], ...aliases.map((alias) => [alias, delegation])];
            });
            const delegationsForDeanonInfoSharing = delegationsDetails.filter(([_, delegationInfo]) => {
                // Drop fully explicit ones: they don't need de-anonymization info
                return !delegationInfo.fullyExplicit;
            });
            // A subset of delegations for which deanon info is relevant AND for which we can also create new info instead of sharing only existing one.
            const delegationsForNewDeanonInfoCreation = delegationsForDeanonInfoSharing.filter(([_, delegationInfo]) => {
                // Drop those for which we don't have the full information needed for the creation of new data.
                return !!delegationInfo.delegate && !!delegationInfo.delegator && !!delegationInfo.accessControlSecret;
            });
            const existingDelegationsMap = yield this.getDecryptedSecureDelegationKeyMaps(delegationsForDeanonInfoSharing.map((x) => x[0]), entityWithType.type);
            for (const delMapToShare of existingDelegationsMap) {
                yield this.ensureDelegationKeyMapSharedWith(entityWithType.type, delMapToShare, shareWithDataOwners);
            }
            const existingDelegationsMapKeys = new Set(existingDelegationsMap.map((x) => x.delegationKey));
            const delegationsToCreate = delegationsForNewDeanonInfoCreation.filter(([k, _]) => !existingDelegationsMapKeys.has(k));
            for (const [delKey, membersDetails] of delegationsToCreate) {
                yield this.createSecureDelegationKeyMap(entityWithType.type, delKey, membersDetails, shareWithDataOwners);
            }
        });
    }
    /**
     * Get the data owners which can access the entity. See {@link EncryptedEntityXApi.getDataOwnersWithAccessTo} for more details.
     * @param entityWithType an entity.
     */
    getDataOwnersWithAccessTo(entityWithType) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            return this.mergePermissions([
                yield this.getDataOwnersWithAccessToSecureDelegations(entityWithType),
                {
                    permissionsByDataOwnerId: Object.fromEntries(Object.keys((_a = entityWithType.entity.delegations) !== null && _a !== void 0 ? _a : {}).map((k) => [k, AccessLevelEnum.WRITE])),
                    hasUnknownAnonymousDataOwners: false,
                },
            ]);
        });
    }
    getDataOwnersWithAccessToSecureDelegations(entityWithType) {
        return __awaiter(this, void 0, void 0, function* () {
            const secureDelegationDetails = yield this.securityMetadataDecryptor.getDelegationMemberDetails(entityWithType);
            const secureDelegationWithUnknownMembers = Object.entries(secureDelegationDetails).flatMap(([canonicalKey, delegation]) => {
                var _a, _b;
                if (!delegation.delegate || !delegation.delegator) {
                    const aliases = Object.entries((_b = (_a = entityWithType.entity.securityMetadata) === null || _a === void 0 ? void 0 : _a.keysEquivalences) !== null && _b !== void 0 ? _b : {}).flatMap(([alias, canon]) => {
                        if (canon == canonicalKey) {
                            return [alias];
                        }
                        else {
                            return [];
                        }
                    });
                    return [{ keys: [canonicalKey, ...aliases], delegation }];
                }
                else {
                    return [];
                }
            });
            const secureDelegationKeyMapsByDelegationKey = Object.fromEntries((yield this.getDecryptedSecureDelegationKeyMaps(secureDelegationWithUnknownMembers.flatMap(({ keys }) => keys), entityWithType.type)).map((x) => [x.delegationKey, x]));
            const permissionsByDataOwnerId = {};
            let hasUnknownAnonymousDataOwners = false;
            function addAccess(dataOwnerId, level) {
                if (permissionsByDataOwnerId[dataOwnerId] !== AccessLevelEnum.WRITE) {
                    permissionsByDataOwnerId[dataOwnerId] = level;
                }
            }
            for (const delegation of Object.values(secureDelegationDetails)) {
                if (delegation.delegate)
                    addAccess(delegation.delegate, delegation.accessLevel);
                if (delegation.delegator)
                    addAccess(delegation.delegator, delegation.accessLevel);
            }
            for (const { keys, delegation } of secureDelegationWithUnknownMembers) {
                const bestKey = keys.find((k) => {
                    const currMap = secureDelegationKeyMapsByDelegationKey[k];
                    return currMap && !!currMap.delegator && !!currMap.delegate;
                });
                if (bestKey) {
                    const keyMap = secureDelegationKeyMapsByDelegationKey[bestKey];
                    addAccess(keyMap.delegate, delegation.accessLevel);
                    addAccess(keyMap.delegator, delegation.accessLevel);
                }
                else {
                    hasUnknownAnonymousDataOwners = true;
                }
            }
            return { permissionsByDataOwnerId, hasUnknownAnonymousDataOwners };
        });
    }
    mergePermissions(values) {
        return __awaiter(this, void 0, void 0, function* () {
            const accumulatedPermissions = {};
            let hasUnknownAnonymousDataOwners = false;
            for (const v of values) {
                hasUnknownAnonymousDataOwners = hasUnknownAnonymousDataOwners || v.hasUnknownAnonymousDataOwners;
                for (const [dataOwnerId, level] of Object.entries(v.permissionsByDataOwnerId)) {
                    if (accumulatedPermissions[dataOwnerId] !== AccessLevelEnum.WRITE) {
                        accumulatedPermissions[dataOwnerId] = level;
                    }
                }
            }
            return {
                permissionsByDataOwnerId: accumulatedPermissions,
                hasUnknownAnonymousDataOwners,
            };
        });
    }
    getDecryptedSecureDelegationKeyMaps(delegationIds, entityType) {
        return __awaiter(this, void 0, void 0, function* () {
            if (delegationIds.length) {
                const encryptedMaps = yield this.delegationKeyMapApi.getByDelegationKeys({ ids: delegationIds }, yield this.accessControlKeysHeadersProvider.getAccessControlKeysHeaders(entityType));
                const res = [];
                for (const encryptedMap of encryptedMaps) {
                    // Use the original entity type
                    const decryptionResult = (yield this.xapis.tryDecryptEntities([encryptedMap], entityType, (x) => new SecureDelegationKeyMap_1.SecureDelegationKeyMap(x)))[0];
                    if (decryptionResult.decrypted) {
                        res.push(decryptionResult.entity);
                    }
                }
                return res;
            }
            else
                return [];
        });
    }
    // Important: to avoid potentially leaking links between entities of different types the key map calculates the secure delegation keys using the
    // same entity type as the delegation key for which they are mapping information.
    ensureDelegationKeyMapSharedWith(entityType, keyMap, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!keyMap.delegator || !keyMap.delegate)
                throw new Error('Illegal state: key map is missing delegator or delegate info.');
            const dataOwnersWithAccessToMapThroughDelegation = Object.values(yield this.securityMetadataDecryptor.getDelegationMemberDetails({
                type: entityType,
                entity: keyMap,
            }))
                .flatMap((x) => [x.delegate, x.delegator])
                .filter((x) => !!x);
            // Delegator and delegate got access to the entity when it was first created: no need to share with them ever.
            const dataOwnersWithAccessToMap = new Set([keyMap.delegate, keyMap.delegator, ...dataOwnersWithAccessToMapThroughDelegation]);
            const dataOwnersNeedingShare = delegates.filter((x) => !dataOwnersWithAccessToMap.has(x));
            if (dataOwnersNeedingShare.length) {
                ;
                (yield this.xapis.simpleShareOrUpdateEncryptedEntityMetadata({
                    entity: keyMap,
                    type: entityType,
                }, Object.fromEntries(dataOwnersNeedingShare.map((x) => [
                    x,
                    {
                        shareSecretIds: [],
                        shareEncryptionKeys: ShareMetadataBehaviour_1.ShareMetadataBehaviour.REQUIRED,
                        shareOwningEntityIds: ShareMetadataBehaviour_1.ShareMetadataBehaviour.NEVER,
                        requestedPermissions: RequestedPermissionEnum.FULL_READ,
                    },
                ])), (request) => __awaiter(this, void 0, void 0, function* () {
                    return this.delegationKeyMapApi.bulkShareSecureDelegationKeyMap(request, yield this.accessControlKeysHeadersProvider.getAccessControlKeysHeaders(entityType));
                }))).updatedEntityOrThrow;
            }
        });
    }
    // Important: to avoid potentially leaking links between entities of different types the key map calculates the secure delegation keys using the
    // same entity type as the delegation key for which they are mapping information.
    createSecureDelegationKeyMap(entityType, delegationKey, delegationMembersDetails, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!delegationMembersDetails.delegate || !delegationMembersDetails.delegator || !delegationMembersDetails.accessControlSecret)
                throw new Error('Illegal state: delegation members details are missing delegate, delegator or access control secret info.');
            const selfDoId = yield this.dataOwnerApi.getCurrentDataOwnerId();
            // Ensure that both the delegator and delegate of the delegation this map refers tho can share it later on, even if they did not create it.
            // Usually either the delegator or delegate are the current data owner, but sometimes also the child of the delegator or delegate can do it.
            const initialDelegates = [delegationMembersDetails.delegate, delegationMembersDetails.delegator, ...delegates].filter((x) => x != selfDoId);
            const initalisedMapInfo = yield this.xapis.entityWithInitialisedEncryptedMetadata({
                id: this.cryptoPrimitives.randomUuid(),
                delegate: delegationMembersDetails.delegate,
                delegator: delegationMembersDetails.delegator,
                delegationKey: delegationKey,
            }, entityType, undefined, undefined, true, Object.fromEntries(initialDelegates.map((x) => [x, AccessLevelEnum.READ])), undefined);
            const encryptedKeyMap = (yield this.xapis.tryEncryptEntities([initalisedMapInfo.updatedEntity], entityType, this.delegationKeyMapFieldsToEncrypt, false, true, (x) => new SecureDelegationKeyMap_1.SecureDelegationKeyMap(x)))[0];
            yield this.delegationKeyMapApi.create(encryptedKeyMap, new XHR_1.XHR.Header(AccessControlKeysHeadersProvider_1.ACCESS_CONTROL_KEYS_HEADER, yield this.accessControlSecretUtils.getEncodedAccessControlKeys([delegationMembersDetails.accessControlSecret], entityType)));
        });
    }
}
exports.DelegationsDeAnonymization = DelegationsDeAnonymization;
//# sourceMappingURL=DelegationsDeAnonymization.js.map