import { DwnInterfaceName } from '../enums/dwn-interface-method.js';
import { EncryptionProtocol } from '../protocols/encryption.js';
import { GrantAuthorization } from './grant-authorization.js';
import { PermissionScopeMatcher } from '../utils/permission-scope.js';
import { PermissionsProtocol } from '../protocols/permissions.js';
import { Records } from '../utils/records.js';
import { DwnError, DwnErrorCode } from './dwn-error.js';
export class MessagesGrantAuthorization {
    static async fetchPermissionGrants(tenant, validationStateReader, permissionGrantIds) {
        return Promise.all(permissionGrantIds.map(permissionGrantId => validationStateReader.fetchGrant(tenant, permissionGrantId)));
    }
    /**
     * Authorizes a MessagesReadMessage using the given permission grant.
     * @param validationStateReader Used to check grant revocation and fetch related RecordsWrites if needed.
     */
    static async authorizeMessagesRead(input) {
        const { messagesReadMessage, messageToRead, expectedGrantor, expectedGrantee, permissionGrants, validationStateReader } = input;
        await MessagesGrantAuthorization.performBaseValidationForGrantSet({
            incomingMessage: messagesReadMessage,
            expectedGrantor,
            expectedGrantee,
            permissionGrants,
            validationStateReader
        });
        for (const permissionGrant of permissionGrants) {
            const scope = permissionGrant.scope;
            if (await MessagesGrantAuthorization.isScopeAuthorized(expectedGrantor, messageToRead, scope, validationStateReader)) {
                return;
            }
        }
        throw new DwnError(DwnErrorCode.MessagesReadVerifyScopeFailed, 'record message failed scope authorization');
    }
    /**
     * Authorizes the scope of a permission grant for MessagesQuery or MessagesSubscribe.
     * @param validationStateReader Used to check if the grant has been revoked.
     */
    static async authorizeQueryOrSubscribe(input) {
        const { incomingMessage, expectedGrantor, expectedGrantee, permissionGrants, validationStateReader } = input;
        await MessagesGrantAuthorization.performBaseValidationForGrantSet({
            incomingMessage,
            expectedGrantor,
            expectedGrantee,
            permissionGrants,
            validationStateReader
        });
        const scopes = permissionGrants.map(permissionGrant => permissionGrant.scope);
        MessagesGrantAuthorization.authorizeFilterScope(incomingMessage, scopes);
    }
    static authorizeFilterScope(messagesMessage, scopes) {
        const { filters } = messagesMessage.descriptor;
        if (filters.length === 0 && !MessagesGrantAuthorization.hasUnscopedGrant(scopes)) {
            throw new DwnError(DwnErrorCode.MessagesGrantAuthorizationUnfilteredSubscribeProtocolScope, `A protocol-scoped grant cannot authorize an unfiltered subscription`);
        }
        for (const filter of filters) {
            if (MessagesGrantAuthorization.someScopeMatches(scopes, { protocol: filter.protocol })) {
                continue;
            }
            throw new DwnError(DwnErrorCode.MessagesGrantAuthorizationSubscribeProtocolMismatch, `No permission grant scope matches protocol ${filter.protocol}`);
        }
    }
    static someScopeMatches(scopes, target) {
        return scopes.some(scope => PermissionScopeMatcher.matches(scope, target));
    }
    static hasUnscopedGrant(scopes) {
        return scopes.some(scope => scope.protocol === undefined);
    }
    /**
     * Revalidates an already-open delegated MessagesSubscribe grant set at event
     * delivery time. Subscription messages are signed at open time, but grant
     * expiry and revocation must stop future delivery after the grant set becomes
     * invalid.
     */
    static async authorizeSubscribeDelivery(input) {
        const { messagesSubscribeMessage, expectedGrantor, expectedGrantee, permissionGrants, validationStateReader, deliveryTimestamp, } = input;
        const deliveryMessage = {
            ...messagesSubscribeMessage,
            descriptor: {
                ...messagesSubscribeMessage.descriptor,
                messageTimestamp: deliveryTimestamp,
            },
        };
        await MessagesGrantAuthorization.authorizeQueryOrSubscribe({
            incomingMessage: deliveryMessage,
            expectedGrantor,
            expectedGrantee,
            permissionGrants,
            validationStateReader,
        });
    }
    /**
     * Performs base validation on every invoked grant. The grant set is all-or-nothing:
     * unresolved, revoked, expired, or interface/method-mismatched grants fail the request.
     */
    static async performBaseValidationForGrantSet(input) {
        const { incomingMessage, expectedGrantor, expectedGrantee, permissionGrants, validationStateReader } = input;
        for (const permissionGrant of permissionGrants) {
            await GrantAuthorization.performBaseValidation({
                incomingMessage,
                expectedGrantor,
                expectedGrantee,
                permissionGrant,
                validationStateReader
            });
        }
    }
    /**
     * Determines whether the given record is inside a grant scope.
     */
    static async isScopeAuthorized(tenant, messageToGet, incomingScope, validationStateReader) {
        if (incomingScope.protocol === undefined) {
            return true;
        }
        if (messageToGet.descriptor.interface === DwnInterfaceName.Records) {
            return MessagesGrantAuthorization.isRecordsMessageScopeAuthorized(tenant, messageToGet, incomingScope, validationStateReader);
        }
        if (messageToGet.descriptor.interface === DwnInterfaceName.Protocols) {
            return MessagesGrantAuthorization.isProtocolsConfigureScopeAuthorized(messageToGet, incomingScope);
        }
        return false;
    }
    static async isRecordsMessageScopeAuthorized(tenant, recordsMessage, incomingScope, validationStateReader) {
        const recordsWriteMessage = await MessagesGrantAuthorization.getAssociatedRecordsWrite(tenant, recordsMessage, validationStateReader);
        if (recordsWriteMessage.descriptor.protocol === PermissionsProtocol.uri) {
            return MessagesGrantAuthorization.isPermissionRecordScopeAuthorized(tenant, recordsWriteMessage, incomingScope, validationStateReader);
        }
        if (recordsWriteMessage.descriptor.protocol === EncryptionProtocol.uri) {
            return MessagesGrantAuthorization.isEncryptionRecordScopeAuthorized(recordsWriteMessage, incomingScope);
        }
        return PermissionScopeMatcher.matches(incomingScope, MessagesGrantAuthorization.getRecordsScopeTarget(recordsWriteMessage));
    }
    static async isPermissionRecordScopeAuthorized(tenant, recordsWriteMessage, incomingScope, validationStateReader) {
        if (MessagesGrantAuthorization.isSubtreeScope(incomingScope)) {
            return false;
        }
        const permissionScope = await PermissionsProtocol.getScopeFromPermissionRecord(tenant, validationStateReader, recordsWriteMessage);
        return PermissionsProtocol.hasProtocolScope(permissionScope)
            && PermissionScopeMatcher.matches(incomingScope, permissionScope);
    }
    static isEncryptionRecordScopeAuthorized(recordsWriteMessage, incomingScope) {
        const tags = recordsWriteMessage.descriptor.tags;
        const protocol = tags?.protocol;
        if (typeof protocol !== 'string') {
            return false;
        }
        return PermissionScopeMatcher.matches(incomingScope, {
            protocol,
            protocolPath: MessagesGrantAuthorization.getStringTag(recordsWriteMessage, 'protocolPath'),
            contextId: MessagesGrantAuthorization.getStringTag(recordsWriteMessage, 'contextId'),
        });
    }
    static isProtocolsConfigureScopeAuthorized(protocolsConfigureMessage, incomingScope) {
        // A delegate with any Messages.Read grant inside a protocol needs that
        // protocol definition to interpret and validate the records it can read.
        return incomingScope.protocol !== undefined &&
            incomingScope.protocol === protocolsConfigureMessage.descriptor.definition.protocol;
    }
    static async getAssociatedRecordsWrite(tenant, recordsMessage, validationStateReader) {
        if (Records.isRecordsWrite(recordsMessage)) {
            return recordsMessage;
        }
        return validationStateReader.fetchNewestRecordsWrite(tenant, recordsMessage.descriptor.recordId);
    }
    static getRecordsScopeTarget(recordsWriteMessage) {
        const { protocol, protocolPath } = recordsWriteMessage.descriptor;
        const { contextId } = recordsWriteMessage;
        return { protocol, protocolPath, contextId };
    }
    static getStringTag(recordsWriteMessage, tag) {
        const value = recordsWriteMessage.descriptor.tags?.[tag];
        return typeof value === 'string' ? value : undefined;
    }
    static isSubtreeScope(scope) {
        return scope.protocolPath !== undefined || scope.contextId !== undefined;
    }
}
//# sourceMappingURL=messages-grant-authorization.js.map