from typing import Optional, List from datetime import date from flask import g from sqlalchemy import and_, literal, func from db import db from services.permissions.exceptions import ( ProjectAccessRestricted, ProjectCreationRestricted, ProjectEditingRestricted, ProjectDeletionRestricted, CampaignCreationRestricted, CampaignEditingRestricted, CampaignDeletionRestricted, ProjectViewTeamRestricted, ProjectManageTeamRestricted, UnassignedProjectClaimRestricted, ProjectNotFound, CampaignNotFound, NotApprovedCampaignEditRestricted, AssignCampaignRestricted, ConfidentialProjectRestricted, UnclaimnedProjectAccessRestricted, ArtistTeamCreationRestricted, ArtistTeamEditingRestricted, UserHasNoAccessToLabel, UserIsNotAnApprover, UnassignedCampaignAccessRestricted, ) from models.projects import Project from models.media_plans import MediaPlan from models.media_plan_phases import MediaPlanPhase from models.campaign import Campaign from models.user import user_label from models.projects import ProjectCampaignStatus, ProjectCampaign from services.permission_role_service import PermissionsByRoleService from models.artists import Artist from models.user_project import UserProjectRoles from models.artist_team import ArtistTeam, ArtistTeamUser from models.campaign_approval import ApprovalRequestCampaign, ApprovalRequest from services.users_repository import UsersRepository from utils.exceptions import Forbidden class PermissionsService: permission_role_service = PermissionsByRoleService() users_repository = UsersRepository() def can_manage_artist_team(self, artist_external_id: str, user_id: int, label_id: int) -> bool: if self.is_admin_for_label(user_id, label_id): return True if self.__is_unknown_artist(artist_external_id): raise ArtistTeamCreationRestricted(detail="You cant create a team for unknown artist") artist_team = self.__get_artist_team(artist_external_id, label_id) if artist_team is None and not g.is_admin: raise ArtistTeamCreationRestricted() artist_team_user = self.__get_artist_team_user(artist_team, user_id) is_editor = artist_team_user and artist_team_user.role == UserProjectRoles.EDITOR.value if not is_editor and not g.is_admin: raise ArtistTeamEditingRestricted() def is_admin_for_label(self, user_id: int, label_id: int): if g.is_admin: label_ids = db.session.query(user_label.c.label_id).filter(user_label.c.user_id == user_id).all() return label_id in label_ids return False def can_manage_ad_accounts(self, label_id: int): self.can_access_label(label_id=label_id) if not g.is_admin: raise Forbidden(f"User {g.user_id} can't manage label {label_id}") def __get_artist_team(self, artist_external_id: str, label_id: int) -> Optional[ArtistTeam]: return ( db.session.query(ArtistTeam) .join( Artist, and_( Artist.external_id == artist_external_id, ArtistTeam.artist_id == Artist.id, Artist.is_unknown.is_(False), ), ) .filter(ArtistTeam.label_id == label_id) .one_or_none() ) def __is_unknown_artist(self, artist_external_id: str) -> bool: query = db.session.query(Artist).filter(Artist.external_id == artist_external_id, Artist.is_unknown.is_(True)) return db.session.query(literal(True)).filter(query.exists()).scalar() def __get_artist_team_user(self, artist_team: ArtistTeam, user_id: int) -> Optional[ArtistTeamUser]: if not artist_team: return None return ( db.session.query(ArtistTeamUser) .filter(ArtistTeamUser.artist_team_id == artist_team.id, ArtistTeamUser.user_id == user_id) .one_or_none() ) def can_access_project(self, project_id: int): project = self.__get_project_by_id(project_id) can_view = self.permission_role_service.can_view_project(g.user_id, project) if not can_view: exception_details = self._get_access_restricted_details(project) if project.is_claimed: raise ProjectAccessRestricted(exception_details) else: raise UnclaimnedProjectAccessRestricted(exception_details) def can_access_media_plan(self, media_plan_id: int): project = self.__get_project_by_media_plan_id(media_plan_id) can_view = self.permission_role_service.can_view_project(g.user_id, project) if not can_view: exception_details = self._get_access_restricted_details(project) if project.is_claimed: raise ProjectAccessRestricted(exception_details) else: raise UnclaimnedProjectAccessRestricted(exception_details) def can_create_project_in_label(self, label_id: int): if not self.permission_role_service.can_access_label(g.user_id, label_id): raise ProjectCreationRestricted() def can_update_media_plan(self, project_id: int): project = self.__get_project_by_id(project_id) can_view = self.permission_role_service.can_view_project(g.user_id, project) editor_or_higher = self.permission_role_service.is_approver_or_editor_and_higher(g.user_id, project) if not can_view: exception_details = self._get_access_restricted_details(project) if project.is_claimed: raise ProjectAccessRestricted(exception_details) else: raise UnclaimnedProjectAccessRestricted(exception_details) if not editor_or_higher: raise ProjectEditingRestricted() def can_update_project(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise ProjectEditingRestricted() def can_update_media_plan_release_details(self, media_plan_id: int): project = self.__get_project_by_media_plan_id(media_plan_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise ProjectEditingRestricted() def can_update_phases(self, media_plan_id: int): project = self.__get_project_by_media_plan_id(media_plan_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise ProjectEditingRestricted() def can_delete_project(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise ProjectDeletionRestricted() def can_create_campaign_by_media_plan_id(self, by_media_plan_id: int, params=None): project = self.__get_project_by_media_plan_id(by_media_plan_id) if (not self.permission_role_service.is_editor_or_higher(g.user_id, project) and (params and not self.can_update_campaign_on_review(params, g.user_id))): raise CampaignCreationRestricted() def can_create_campaign(self, project_id: int, params=None): project = self.__get_project_by_id(project_id) if (not self.permission_role_service.is_editor_or_higher(g.user_id, project) and (params and not self.can_update_campaign_on_review(params, g.user_id))): raise CampaignCreationRestricted() def can_edit_campaign_by_media_plan_id(self, media_plan_id: int, campaign_uuid: str): project = self.__get_project_by_media_plan_id(media_plan_id) campaign = self.__get_campaign_by_uuid(campaign_uuid=campaign_uuid, filter_deleted=True) project_campaign = self.__project_campaign(project, campaign) if project_campaign and project_campaign.status != ProjectCampaignStatus.APPROVED.value: raise NotApprovedCampaignEditRestricted() if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise CampaignEditingRestricted() def can_edit_campaign(self, project_id: int, campaign_uuid: str): project = self.__get_project_by_id(project_id) campaign = self.__get_campaign_by_uuid(campaign_uuid=campaign_uuid, filter_deleted=True) project_campaign = self.__project_campaign(project, campaign) if project_campaign and project_campaign.status != ProjectCampaignStatus.APPROVED.value: raise NotApprovedCampaignEditRestricted() if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise CampaignEditingRestricted() def can_delete_campaign(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise CampaignDeletionRestricted() def can_view_team(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.can_view_team(g.user_id, project): raise ProjectViewTeamRestricted() def can_manage_team(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise ProjectManageTeamRestricted() def can_view_unassigned_project(self, project_id: int): unclaimed_project = self.__get_unclaimed_project(project_id) if not unclaimed_project: raise ProjectNotFound() if unclaimed_project.is_confidential and not g.is_admin: raise ConfidentialProjectRestricted() def can_claim_project(self, project_id: int): unclaimed_project = self.__get_unclaimed_project(project_id) if unclaimed_project is None: raise UnassignedProjectClaimRestricted() if not (unclaimed_project.is_confidential and g.is_admin): target_item = unclaimed_project.prs_artist() if target_item is not None and not target_item.entity.is_unknown: raise UnassignedProjectClaimRestricted() def can_assign_campaign_to_project(self, project_id: int): project = self.__get_project_by_id(project_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise AssignCampaignRestricted() def can_access_label(self, label_id: int): if self.users_repository.get_user_label(g.user_id, label_id) is None: raise UserHasNoAccessToLabel() def __get_project_by_id(self, project_id: int) -> Project: project = db.session.query(Project).filter(Project.id == project_id).one_or_none() if not project: raise ProjectNotFound() return project def __get_project_by_media_plan_id(self, media_plan_id: int) -> Project: project = ( db.session.query(Project) .select_from(MediaPlan) .join(Project, Project.id == MediaPlan.project_id) .filter(MediaPlan.id == media_plan_id) .one_or_none() ) if not project: raise ProjectNotFound() return project def __get_campaign_by_id(self, campaign_id: int, filter_deleted: bool) -> Campaign: campaign_query = db.session.query(Campaign).filter(Campaign.id == campaign_id) if filter_deleted: campaign_query = campaign_query.filter(Campaign.is_deleted.is_(False)) campaign = campaign_query.one_or_none() if not campaign: raise CampaignNotFound() return campaign def __project_campaign(self, project: Project, campaign: Campaign) -> Optional[ProjectCampaign]: if not project.is_claimed: return None return ( db.session.query(ProjectCampaign).filter( ProjectCampaign.campaign_id == campaign.id, ProjectCampaign.project_id == project.id ) ).one_or_none() def __get_unclaimed_project(self, project_id: int) -> Optional[Project]: return ( db.session.query(Project) .join(user_label, and_(user_label.c.label_id == Project.label_id, user_label.c.user_id == g.user_id)) .filter(and_(Project.id == project_id, Project.is_claimed.is_(False), Project.is_deleted.is_(False))) .one_or_none() ) def __get_campaign_by_uuid(self, campaign_uuid: str, filter_deleted: bool) -> Campaign: campaign_query = db.session.query(Campaign).filter(Campaign.uuid == campaign_uuid) if filter_deleted: campaign_query = campaign_query.filter(Campaign.is_deleted.is_(False)) campaign = campaign_query.one_or_none() if not campaign: raise CampaignNotFound() return campaign def __get_campaign_by_project_id_and_uuid( self, campaign_uuid: str, project_id: int, filter_deleted: bool ) -> Campaign: campaign_query = db.session.query(Campaign).filter( Campaign.uuid == campaign_uuid, Campaign.project_id == project_id ) if filter_deleted: campaign_query = campaign_query.filter(Campaign.is_deleted.is_(False)) campaign = campaign_query.one_or_none() if not campaign: raise CampaignNotFound() return campaign def __get_campaigns_by_media_plan_id_and_uuids( self, campaign_uuids: List[str], media_plan_id: int, ): return ( db.session.query(func.array_agg(Campaign.uuid)) .select_from(Campaign) .join(MediaPlanPhase, Campaign.media_plan_phase_id == MediaPlanPhase.id) .join(MediaPlan, and_(MediaPlanPhase.media_plan_id == MediaPlan.id, MediaPlan.id == media_plan_id)) .filter( Campaign.uuid.in_(campaign_uuids), Campaign.is_deleted.is_(False) ).one() ) def can_create_approval_request(self, media_plan_id: int, approval_request_data): project = self.__get_project_by_media_plan_id(media_plan_id) if not self.permission_role_service.is_editor_or_higher(g.user_id, project): raise CampaignCreationRestricted() campaign_uuids = approval_request_data.campaigns active_campaigns = self.__get_campaigns_by_media_plan_id_and_uuids(campaign_uuids, media_plan_id) if active_campaigns is None or active_campaigns[0] is None: raise CampaignNotFound() campaigns_diff = set(campaign_uuids).symmetric_difference(set(active_campaigns[0])) if campaigns_diff: raise CampaignNotFound(list(campaigns_diff)) def can_submit_approval_request(self, media_plan_id: int): project = self.__get_project_by_media_plan_id(media_plan_id) if not self.permission_role_service.can_submit_approval_request(g.user_id, project): raise UserIsNotAnApprover() def _get_access_restricted_details(self, project): project_editors = self.permission_role_service.get_editors_for_project(project) return { "projectName": project.display_name, "projectEditors": project_editors, } def __get_approval_request_campaign(self, reviewer_id, campaign_uuid): return ( db.session.query(ApprovalRequestCampaign) .join(ApprovalRequest, ApprovalRequestCampaign.approval_request) .filter( ApprovalRequestCampaign.campaign_uuid == campaign_uuid, ApprovalRequestCampaign.is_deleted.is_(False), ApprovalRequestCampaign.completed_at.is_(None), ApprovalRequestCampaign.expiration_date > date.today(), ApprovalRequest.reviewer_id == reviewer_id ) .one_or_none() ) def can_update_campaign_on_review(self, params, user_id): for campaign in params.update: if self.__get_approval_request_campaign(user_id, campaign.campaign_uuid): return True return False def can_access_unassigned_campaign(self, campaign_uuid): campaign = self.__get_campaign_by_uuid(campaign_uuid=campaign_uuid, filter_deleted=True) if not campaign: raise CampaignNotFound() if self.users_repository.get_user_label(g.user_id, campaign.label_id) is None: raise UnassignedCampaignAccessRestricted() permissions = PermissionsService()