From 5f58417d24ae00c94d821443d4ee43796f4acd4b Mon Sep 17 00:00:00 2001 From: Josako Date: Tue, 15 Apr 2025 17:12:46 +0200 Subject: [PATCH] - Add 'Partner Admin' role to actual functionality in eveai_app --- common/services/tenant_service.py | 71 ++++++++++++++++ common/services/user_service.py | 21 ++--- common/utils/eveai_exceptions.py | 32 ++++++++ common/utils/middleware.py | 31 ++++--- eveai_app/templates/navbar.html | 40 ++++----- eveai_app/views/basic_views.py | 2 +- eveai_app/views/document_views.py | 48 +++++------ eveai_app/views/entitlements_views.py | 12 +-- eveai_app/views/interaction_views.py | 36 ++++----- eveai_app/views/security_views.py | 2 +- eveai_app/views/user_forms.py | 9 ++- eveai_app/views/user_views.py | 112 ++++++++++++++++---------- 12 files changed, 281 insertions(+), 135 deletions(-) create mode 100644 common/services/tenant_service.py diff --git a/common/services/tenant_service.py b/common/services/tenant_service.py new file mode 100644 index 0000000..a925b60 --- /dev/null +++ b/common/services/tenant_service.py @@ -0,0 +1,71 @@ +from flask import session, current_app +from sqlalchemy.exc import SQLAlchemyError + +from common.extensions import db +from common.models.user import Partner, PartnerTenant +from common.utils.eveai_exceptions import EveAINoManagementPartnerService +from common.utils.model_logging_utils import set_logging_information +from datetime import datetime as dt, timezone as tz + +from common.utils.security_utils import current_user_has_role + + +class TenantService: + @staticmethod + def associate_tenant_with_partner(tenant_id): + """Associate a tenant with a partner""" + try: + partner_id = session['partner']['id'] + # Get partner service (MANAGEMENT_SERVICE type) + partner = Partner.query.get(partner_id) + if not partner: + return + + # Find a management service for this partner + management_service = next((service for service in session['partner']['services'] + if service.get('type') == 'MANAGEMENT_SERVICE'), None) + + if not management_service: + current_app.logger.error(f"No Management Service defined for partner {partner_id}" + f"while associating tenant {tenant_id} with partner.") + raise EveAINoManagementPartnerService() + + # Create the association + tenant_partner = PartnerTenant( + partner_service_id=management_service['id'], + tenant_id=tenant_id, + relationship_type='MANAGED', + ) + set_logging_information(tenant_partner, dt.now(tz.utc)) + + db.session.add(tenant_partner) + db.session.commit() + + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f"Error associating tenant {tenant_id} with partner: {str(e)}") + raise e + + @staticmethod + def can_user_edit_tenant(tenant_id) -> bool: + if current_user_has_role('Super User'): + return True + elif current_user_has_role('Partner Admin'): + partner_id = session['partner']['id'] + partner_service = next((service for service in session['partner']['services'] + if service.get('type') == 'MANAGEMENT_SERVICE'), None) + if not partner_service: + return False + else: + partner_tenant = PartnerTenant.query.filter( + PartnerTenant.tenant_id == tenant_id, + PartnerTenant.partner_service_id == partner_service['id'], + ).first() + if partner_tenant: + return True + else: + return False + else: + return False + + diff --git a/common/services/user_service.py b/common/services/user_service.py index beba4c3..be666ff 100644 --- a/common/services/user_service.py +++ b/common/services/user_service.py @@ -14,26 +14,21 @@ class UserService: and the active tenant for the session""" current_tenant_id = session.get('tenant').get('id', None) effective_role_names = [] - if current_tenant_id: + if current_tenant_id == 1: if current_user_has_role("Super User"): - if current_tenant_id == 1: - effective_role_names.append("Super User") - if session.get('partner'): - effective_role_names.append("Partner Admin") - effective_role_names.append("Tenant Admin") + effective_role_names.append("Super User") + elif current_tenant_id: if current_user_has_role("Tenant Admin"): effective_role_names.append("Tenant Admin") - if current_user_has_role("Partner Admin"): + if current_user_has_role("Partner Admin") or current_user_has_role("Super User"): effective_role_names.append("Tenant Admin") if session.get('partner'): if session.get('partner').get('tenant_id') == current_tenant_id: effective_role_names.append("Partner Admin") - effective_role_names = list(set(effective_role_names)) - effective_roles = [(role.id, role.name) for role in - Role.query.filter(Role.name.in_(effective_role_names)).all()] - return effective_roles - else: - return [] + effective_role_names = list(set(effective_role_names)) + effective_roles = [(role.id, role.name) for role in + Role.query.filter(Role.name.in_(effective_role_names)).all()] + return effective_roles @staticmethod def validate_role_assignments(role_ids): diff --git a/common/utils/eveai_exceptions.py b/common/utils/eveai_exceptions.py index 77eab11..9622661 100644 --- a/common/utils/eveai_exceptions.py +++ b/common/utils/eveai_exceptions.py @@ -154,3 +154,35 @@ class EveAIRoleAssignmentException(EveAIException): def __init__(self, message, status_code=403, payload=None): super().__init__(message, status_code, payload) + +class EveAINoManagementPartnerService(EveAIException): + """Exception raised when the operation requires the logged in partner (or selected parter by Super User) + does not have a MANAGEMENT_SERVICE""" + + def __init__(self, message="No Management Service defined for partner", status_code=403, payload=None): + super().__init__(message, status_code, payload) + + +class EveAINoSessionTenant(EveAIException): + """Exception raised when no session tenant is set""" + + def __init__(self, message="No Session Tenant selected. Cannot perform requested action.", status_code=403, + payload=None): + super().__init__(message, status_code, payload) + + +class EveAINoSessionPartner(EveAIException): + """Exception raised when no session partner is set""" + + def __init__(self, message="No Session Partner selected. Cannot perform requested action.", status_code=403, + payload=None): + super().__init__(message, status_code, payload) + + +class EveAINoManagementPartnerForTenant(EveAIException): + """Exception raised when the selected partner is no management partner for tenant""" + + def __init__(self, message="No Management Partner for Tenant", status_code=403, payload=None): + super().__init__(message, status_code, payload) + + diff --git a/common/utils/middleware.py b/common/utils/middleware.py index 4722817..5e2b67b 100644 --- a/common/utils/middleware.py +++ b/common/utils/middleware.py @@ -5,9 +5,10 @@ for handling tenant requests from flask_security import current_user from flask import session, current_app, redirect -from common.utils.nginx_utils import prefixed_url_for - from .database import Database +from .eveai_exceptions import EveAINoSessionTenant, EveAINoSessionPartner, EveAINoManagementPartnerService, \ + EveAINoManagementPartnerForTenant +from ..services.tenant_service import TenantService def mw_before_request(): @@ -17,17 +18,27 @@ def mw_before_request(): """ if 'tenant' not in session: - current_app.logger.warning('No tenant defined in session') - return redirect(prefixed_url_for('security_bp.login')) + raise EveAINoSessionTenant() tenant_id = session['tenant']['id'] if not tenant_id: - raise Exception('Cannot switch schema for tenant: no tenant defined in session') + raise EveAINoSessionTenant() - # user = User.query.get(current_user.id) - if current_user.has_role('Super User') or current_user.tenant_id == tenant_id: - Database(tenant_id).switch_schema() - else: - raise Exception(f'Cannot switch schema for tenant {tenant_id}: user {current_user.email} does not have access') + switch_allowed = False + if current_user.has_role('Super User'): + switch_allowed = True + if current_user.has_role('Tenant Admin') and current_user.tenant_id == tenant_id: + switch_allowed = True + if current_user.has_role('Partner Admin'): + if 'partner' not in session: + raise EveAINoSessionPartner() + management_service = next((service for service in session['partner']['services'] + if service.get('type') == 'MANAGEMENT_SERVICE'), None) + if not management_service: + raise EveAINoManagementPartnerService() + if not TenantService.can_user_edit_tenant(tenant_id): + raise EveAINoManagementPartnerForTenant() + + Database(tenant_id).switch_schema() diff --git a/eveai_app/templates/navbar.html b/eveai_app/templates/navbar.html index 7b2ef81..0f8d45f 100644 --- a/eveai_app/templates/navbar.html +++ b/eveai_app/templates/navbar.html @@ -69,38 +69,38 @@