"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChannelPermission = exports.CHANNEL_ACTION_PRIVS = void 0;
const platform_1 = require("./platform");
const channel_to_dto_map_1 = require("./channels/channel-to-dto-map");
const constants_1 = require("../../constants");
const aclCategory_1 = require("../enums/aclCategory");
const role_1 = require("../enums/role");
const aclSubCategory_1 = require("../enums/aclSubCategory");
const CHANNEL_ACTION = {
    READ_POSTS: "readPosts",
    WRITE_POSTS: "writePosts",
    MODERATE_CHANNEL: "moderateChannel",
    DELETE_CHANNEL: "deleteChannel",
    IS_MODERATOR: "isModerator",
    IS_MANAGER: "isManager",
    IS_OWNER: "isOwner",
};
/**
 * A map of update operations & ACL roles where the key is the update operation
 * and the value is an array of roles that are permitted to perform the update operation.
 * See confluence for privs documentation: https://confluencewikidev.esri.com/pages/viewpage.action?pageId=153747776#Roles&Privileges-ApplicationtoChannels
 * @internal
 * @hidden
 */
exports.CHANNEL_ACTION_PRIVS = {
    // permissions
    UPDATE_OWNERS: [role_1.Role.OWNER],
    UPDATE_MANAGERS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_MODERATORS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_ORGS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_GROUPS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_USERS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_AUTHENTICATED_USERS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_ANONYMOUS_USERS: [role_1.Role.OWNER, role_1.Role.MANAGE],
    // settings
    UPDATE_POST_REPLIES: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_POST_REACTIONS: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_POST_AS_ANONYMOUS: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_ALLOWED_REACTIONS: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_DEFAULT_POST_STATUS: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_BLOCKED_WORDS: [role_1.Role.OWNER, role_1.Role.MANAGE, role_1.Role.MODERATE],
    UPDATE_CHANNEL_NAME: [role_1.Role.OWNER, role_1.Role.MANAGE],
    UPDATE_SOFT_DELETE_SETTING: [role_1.Role.OWNER, role_1.Role.MANAGE],
};
/**
 * @internal
 * @hidden
 */
class ChannelPermission {
    constructor(channel) {
        this.ALLOWED_GROUP_MEMBER_TYPES = ["owner", "admin", "member"];
        if (channel.channelAcl === undefined) {
            throw new Error("channel.channelAcl is required for ChannelPermission checks");
        }
        this.existingChannel = channel;
        this.isChannelAclEmpty = channel.channelAcl.length === 0;
        this.permissionsByCategory = {};
        channel.channelAcl.forEach((permission) => {
            var _c;
            const { category } = permission;
            // if you are updating this file, remove this comment and address the lint error
            // eslint-disable-next-line @typescript-eslint/no-unused-expressions
            ((_c = this.permissionsByCategory[category]) === null || _c === void 0 ? void 0 : _c.push(permission)) ||
                (this.permissionsByCategory[category] = [permission]);
        });
    }
    deriveUserRole(user) {
        return this.determineUserRole(user);
    }
    canPostToChannel(user) {
        if (this.canAnyUser(CHANNEL_ACTION.WRITE_POSTS)) {
            return true;
        }
        if (this.isUserUnAuthenticated(user)) {
            return false;
        }
        return (this.canAnyAuthenticatedUser(CHANNEL_ACTION.WRITE_POSTS) ||
            this.canSomeUser(CHANNEL_ACTION.WRITE_POSTS, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.WRITE_POSTS, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.WRITE_POSTS, user));
    }
    canCreateChannel(user) {
        if (this.isUserUnAuthenticated(user) || this.isChannelAclEmpty) {
            return false;
        }
        return (this.userCanAddAnonymousToAcl(user) &&
            this.userCanAddUnauthenticatedToAcl(user) &&
            this.userCanAddAllGroupsToAcl(user) &&
            this.userCanAddAllOrgsToAcl(user) &&
            this.userCanAddUsersToAcl(user));
    }
    canModerateChannel(user) {
        if (this.isUserUnAuthenticated(user)) {
            return false;
        }
        return (this.canSomeUser(CHANNEL_ACTION.MODERATE_CHANNEL, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.MODERATE_CHANNEL, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.MODERATE_CHANNEL, user));
    }
    canReadChannel(user) {
        if (this.canAnyUser(CHANNEL_ACTION.READ_POSTS)) {
            return true;
        }
        if (this.isUserUnAuthenticated(user)) {
            return false;
        }
        return (this.canAnyAuthenticatedUser(CHANNEL_ACTION.READ_POSTS) ||
            this.canSomeUser(CHANNEL_ACTION.READ_POSTS, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.READ_POSTS, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.READ_POSTS, user));
    }
    canDeleteChannel(user) {
        if (this.isUserUnAuthenticated(user)) {
            return false;
        }
        return (this.canSomeUser(CHANNEL_ACTION.DELETE_CHANNEL, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.DELETE_CHANNEL, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.DELETE_CHANNEL, user));
    }
    /**
     * Ensure the user has rights to update the channel before calling this method
     * Ex: canModerateChannel
     */
    canUpdateProperties(user, updateData = {}) {
        if (Object.keys(updateData).length === 0) {
            return true;
        }
        const userRole = this.determineUserRole(user);
        const updates = (0, channel_to_dto_map_1.dtoToChannel)(updateData);
        if (
        // settings
        (this.isChanged(updates.allowReply, this.existingChannel.allowReply) &&
            !exports.CHANNEL_ACTION_PRIVS.UPDATE_POST_REPLIES.includes(userRole)) ||
            (this.isChanged(updates.allowReaction, this.existingChannel.allowReaction) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_POST_REACTIONS.includes(userRole)) ||
            (this.isChanged(updates.allowAsAnonymous, this.existingChannel.allowAsAnonymous) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_POST_AS_ANONYMOUS.includes(userRole)) ||
            (this.isStringArrayChanged(updates.allowedReactions, this.existingChannel.allowedReactions) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_ALLOWED_REACTIONS.includes(userRole)) ||
            (this.isChanged(updates.defaultPostStatus, this.existingChannel.defaultPostStatus) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_DEFAULT_POST_STATUS.includes(userRole)) ||
            (this.isStringArrayChanged(updates.blockWords, this.existingChannel.blockWords) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_BLOCKED_WORDS.includes(userRole)) ||
            (this.isChanged(updates.name, this.existingChannel.name) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_CHANNEL_NAME.includes(userRole)) ||
            (this.isChanged(updates.softDelete, this.existingChannel.softDelete) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_SOFT_DELETE_SETTING.includes(userRole)) ||
            // permissions
            (this.isRoleChanged(role_1.Role.OWNER, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_OWNERS.includes(userRole)) ||
            (this.isRoleChanged(role_1.Role.MANAGE, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_MANAGERS.includes(userRole)) ||
            (this.isRoleChanged(role_1.Role.MODERATE, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_MODERATORS.includes(userRole)) ||
            (this.isCategoryChanged(aclCategory_1.AclCategory.ORG, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_ORGS.includes(userRole)) ||
            (this.isCategoryChanged(aclCategory_1.AclCategory.GROUP, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_GROUPS.includes(userRole)) ||
            (this.isCategoryChanged(aclCategory_1.AclCategory.USER, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_USERS.includes(userRole)) ||
            (this.isCategoryChanged(aclCategory_1.AclCategory.AUTHENTICATED_USER, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_AUTHENTICATED_USERS.includes(userRole)) ||
            (this.isCategoryChanged(aclCategory_1.AclCategory.ANONYMOUS_USER, updates.channelAcl) &&
                !exports.CHANNEL_ACTION_PRIVS.UPDATE_ANONYMOUS_USERS.includes(userRole))) {
            return false;
        }
        return true;
    }
    isChanged(updateValue, existingValue) {
        return updateValue !== undefined && updateValue !== existingValue;
    }
    isStringArrayChanged(_a, _b) {
        const a = _a !== null && _a !== void 0 ? _a : [];
        const b = _b !== null && _b !== void 0 ? _b : [];
        return (a.filter((x) => !b.includes(x)).length !== 0 ||
            b.filter((x) => !a.includes(x)).length !== 0);
    }
    isRoleChanged(role, aclUpdates) {
        if (!aclUpdates) {
            return false;
        }
        // ex: ['org_admin_1111_owner', 'group_admin_222_owner', 'user_undefined_333_owner]
        const existing = this.existingChannel.channelAcl
            .filter((acl) => acl.role === role)
            .map((acl) => `${acl.category}_${acl.subCategory}_${acl.key}_${role}`);
        const changed = aclUpdates
            .filter((acl) => acl.role === role)
            .map((acl) => `${acl.category}_${acl.subCategory}_${acl.key}_${role}`);
        return this.isStringArrayChanged(existing, changed);
    }
    isCategoryChanged(category, aclUpdates) {
        var _c;
        if (!aclUpdates) {
            return false;
        }
        // ex: ['org_admin_111_OWNER', 'org_member_111_readWrite', 'group_member_2222_read']
        const existing = ((_c = this.permissionsByCategory[category]) !== null && _c !== void 0 ? _c : []).map((acl) => `${category}_${acl.subCategory}_${acl.key}_${acl.role}`);
        const changed = aclUpdates
            .filter((acl) => acl.category === category)
            .map((acl) => `${category}_${acl.subCategory}_${acl.key}_${acl.role}`);
        return this.isStringArrayChanged(existing, changed);
    }
    canAnyUser(action) {
        var _c;
        const anonymousUserRole = (_c = this.permissionsByCategory[aclCategory_1.AclCategory.ANONYMOUS_USER]) === null || _c === void 0 ? void 0 : _c[0].role;
        return channelActionLookup(action).includes(anonymousUserRole);
    }
    canAnyAuthenticatedUser(action) {
        var _c;
        const role = (_c = this.permissionsByCategory[aclCategory_1.AclCategory.AUTHENTICATED_USER]) === null || _c === void 0 ? void 0 : _c[0].role;
        return channelActionLookup(action).includes(role);
    }
    canSomeUser(action, user) {
        var _c;
        const userPermissions = (_c = this.permissionsByCategory[aclCategory_1.AclCategory.USER]) !== null && _c !== void 0 ? _c : [];
        const username = user.username;
        return userPermissions.some((permission) => {
            const { role, key } = permission;
            return key === username && channelActionLookup(action).includes(role);
        });
    }
    canSomeUserGroup(action, user) {
        var _c;
        const groupAccessControls = (_c = this.permissionsByCategory[aclCategory_1.AclCategory.GROUP]) !== null && _c !== void 0 ? _c : [];
        const userGroupsById = this.mapUserGroupsById(user.groups);
        return groupAccessControls.some((permission) => {
            const group = userGroupsById[permission.key];
            if (action === CHANNEL_ACTION.READ_POSTS) {
                if (!group) {
                    return false;
                }
            }
            else {
                if (!group || !isGroupDiscussable(group)) {
                    return false;
                }
            }
            return (doesPermissionAllowGroupMemberType(permission, group) &&
                channelActionLookup(action).includes(permission.role));
        });
    }
    canSomeUserOrg(action, user) {
        var _c;
        const orgPermissions = (_c = this.permissionsByCategory[aclCategory_1.AclCategory.ORG]) !== null && _c !== void 0 ? _c : [];
        return orgPermissions.some((permission) => {
            if (permission.key !== user.orgId) {
                return false;
            }
            return (doesPermissionAllowOrgRole(permission, user) &&
                channelActionLookup(action).includes(permission.role));
        });
    }
    userCanAddAnonymousToAcl(user) {
        if (!this.permissionsByCategory[aclCategory_1.AclCategory.ANONYMOUS_USER]) {
            return true;
        }
        return ((0, platform_1.isOrgAdmin)(user) ||
            (0, platform_1.userHasPrivilege)(user, "portal:admin:shareToPublic") ||
            (0, platform_1.userHasPrivilege)(user, "portal:user:shareToPublic"));
    }
    userCanAddUnauthenticatedToAcl(user) {
        if (!this.permissionsByCategory[aclCategory_1.AclCategory.AUTHENTICATED_USER]) {
            return true;
        }
        return ((0, platform_1.isOrgAdmin)(user) ||
            (0, platform_1.userHasPrivilege)(user, "portal:admin:shareToPublic") ||
            (0, platform_1.userHasPrivilege)(user, "portal:user:shareToPublic"));
    }
    userCanAddAllGroupsToAcl(user) {
        const groupPermissions = this.permissionsByCategory[aclCategory_1.AclCategory.GROUP];
        const userGroupsById = this.mapUserGroupsById(user.groups);
        if (!groupPermissions) {
            return true;
        }
        return groupPermissions.every((permission) => {
            const userGroup = userGroupsById[permission.key];
            return (userGroup &&
                this.isMemberTypeAuthorized(userGroup) &&
                isGroupDiscussable(userGroup));
        });
    }
    userCanAddAllOrgsToAcl(user) {
        const orgPermissions = this.permissionsByCategory[aclCategory_1.AclCategory.ORG];
        if (!orgPermissions) {
            return true;
        }
        return (((0, platform_1.isOrgAdmin)(user) ||
            (0, platform_1.userHasPrivilege)(user, "portal:admin:shareToOrg") ||
            (0, platform_1.userHasPrivilege)(user, "portal:user:shareToOrg")) &&
            this.isEveryPermissionForUserOrg(user.orgId, orgPermissions));
    }
    isEveryPermissionForUserOrg(userOrgId, orgPermissions) {
        return orgPermissions.every((permission) => {
            const { key: orgId } = permission;
            return userOrgId === orgId;
        });
    }
    // for now user permissions are disabled on channel create
    // since users are not notified and cannot opt out
    userCanAddUsersToAcl(_user) {
        const userPermissions = this.permissionsByCategory[aclCategory_1.AclCategory.USER];
        return !userPermissions;
    }
    isUserUnAuthenticated(user) {
        return user.username === null || user.username === undefined;
    }
    mapUserGroupsById(groups) {
        return groups.reduce((accum, userGroup) => {
            accum[userGroup.id] = userGroup;
            return accum;
        }, {});
    }
    isMemberTypeAuthorized(userGroup) {
        const { userMembership: { memberType }, } = userGroup;
        return this.ALLOWED_GROUP_MEMBER_TYPES.includes(memberType);
    }
    determineUserRole(user) {
        if (this.isOwner(user)) {
            return role_1.Role.OWNER;
        }
        else if (this.isManager(user)) {
            return role_1.Role.MANAGE;
        }
        else if (this.isModerator(user)) {
            return role_1.Role.MODERATE;
        }
        else {
            return role_1.Role.READ;
        }
    }
    isOwner(user) {
        return (this.canSomeUser(CHANNEL_ACTION.IS_OWNER, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.IS_OWNER, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.IS_OWNER, user));
    }
    isManager(user) {
        return (this.canSomeUser(CHANNEL_ACTION.IS_MANAGER, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.IS_MANAGER, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.IS_MANAGER, user));
    }
    isModerator(user) {
        return (this.canSomeUser(CHANNEL_ACTION.IS_MODERATOR, user) ||
            this.canSomeUserGroup(CHANNEL_ACTION.IS_MODERATOR, user) ||
            this.canSomeUserOrg(CHANNEL_ACTION.IS_MODERATOR, user));
    }
}
exports.ChannelPermission = ChannelPermission;
function isGroupDiscussable(userGroup) {
    const { typeKeywords = [] } = userGroup;
    return !typeKeywords.includes(constants_1.CANNOT_DISCUSS);
}
function doesPermissionAllowGroupMemberType(permission, group) {
    if (permission.category !== aclCategory_1.AclCategory.GROUP ||
        group.userMembership.memberType === "none") {
        return false;
    }
    return (
    // group owners and admins can do anything permissioned with SubCategory "member"
    group.userMembership.memberType === "owner" ||
        group.userMembership.memberType === "admin" ||
        permission.subCategory === aclSubCategory_1.AclSubCategory.MEMBER);
}
function doesPermissionAllowOrgRole(permission, user) {
    return (permission.category === aclCategory_1.AclCategory.ORG &&
        (permission.subCategory === aclSubCategory_1.AclSubCategory.MEMBER ||
            (permission.subCategory === aclSubCategory_1.AclSubCategory.ADMIN && (0, platform_1.isOrgAdmin)(user))));
}
function channelActionLookup(action) {
    // channel actions
    if (action === CHANNEL_ACTION.WRITE_POSTS) {
        return [role_1.Role.WRITE, role_1.Role.READWRITE, role_1.Role.MODERATE, role_1.Role.MANAGE, role_1.Role.OWNER];
    }
    if (action === CHANNEL_ACTION.MODERATE_CHANNEL) {
        return [role_1.Role.MODERATE, role_1.Role.MANAGE, role_1.Role.OWNER];
    }
    if (action === CHANNEL_ACTION.DELETE_CHANNEL) {
        return [role_1.Role.OWNER];
    }
    // channel update actions
    if (action === CHANNEL_ACTION.IS_MODERATOR) {
        return [role_1.Role.MODERATE];
    }
    if (action === CHANNEL_ACTION.IS_MANAGER) {
        return [role_1.Role.MANAGE];
    }
    if (action === CHANNEL_ACTION.IS_OWNER) {
        return [role_1.Role.OWNER];
    }
    // default to read action
    return [role_1.Role.READ, role_1.Role.READWRITE, role_1.Role.MODERATE, role_1.Role.MANAGE, role_1.Role.OWNER];
}
//# sourceMappingURL=channel-permission.js.map