Source code for pas.security

from __future__ import annotations

from onegov.core.security import Public, Personal, Private
from onegov.core.security.roles import get_roles_setting as \
    get_roles_setting_base
from onegov.pas import PasApp
from onegov.pas.collections import (
    AttendenceCollection
)
from onegov.org.models import GeneralFileCollection

from datetime import date
from onegov.pas.models.attendence import Attendence
from onegov.pas.models.commission import Commission
from onegov.pas.models.parliamentarian import PASParliamentarian
from onegov.org.models import Organisation
from onegov.user import User


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from typing import Any
    from onegov.core.security.roles import Intent
    from morepath import Identity
    from onegov.pas.models.parliamentarian import PASParliamentarian


"""
PAS is fully internal.
    - Admins can do everything.
    - Parliamentarians can report attendance, view limited data.
    - Commission presidents can same as parliamentarians + edit attendance of
      peers in their commission.
    - Editors and Members in the usual sense don't exist.
"""


@PasApp.replace_setting_section(section='roles')
[docs] def get_roles_setting() -> dict[str, set[type[Intent]]]: result = get_roles_setting_base() # All parliamentarians are basically members, plus a few # specific permissions result['parliamentarian'] = {Public, Personal} result['commission_president'] = {Public, Personal} return result
@PasApp.permission_rule(model=object, permission=object)
[docs] def has_permission_logged_in( app: PasApp, identity: Identity, model: Any, permission: Intent ) -> bool: return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=AttendenceCollection, permission=object)
[docs] def restrict_attendence_collection_access( app: PasApp, identity: Identity, model: AttendenceCollection, permission: Intent ) -> bool: if identity.role in ('parliamentarian', 'commission_president'): # Allow Private permission for attendance collection access if isinstance(permission, type) and issubclass(permission, Private): return True return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=Attendence, permission=object)
[docs] def restrict_attendence_access( app: PasApp, identity: Identity, model: Attendence, permission: Intent ) -> bool: # Check basic role permissions first if identity.role not in ('parliamentarian', 'commission_president'): return permission in getattr(app.settings.roles, identity.role) # For parliamentarians and commission presidents, check ownership if identity.role in ('parliamentarian', 'commission_president'): # Allow Private permission only if they have access to this record if isinstance(permission, type) and issubclass(permission, Private): user = app.session().query(User).filter_by( username=identity.userid).first() if not user or not user.parliamentarian: # type: ignore[attr-defined] return False parliamentarian: PASParliamentarian = user.parliamentarian # type: ignore[attr-defined] # Regular parliamentarians can only access their own records if identity.role == 'parliamentarian': return model.parliamentarian_id == parliamentarian.id # Commission presidents can access their own + commission members' elif identity.role == 'commission_president': # Always allow own records if model.parliamentarian_id == parliamentarian.id: return True # Check if the parliamentarian owning this attendance record # is a member of any commission this president leads # Get attendance record owner attendance_owner = app.session().query(User).join( User.parliamentarian # type: ignore[attr-defined] ).filter( User.parliamentarian.has(id=model.parliamentarian_id) # type: ignore[attr-defined] ).first() if attendance_owner and attendance_owner.parliamentarian: # Check if president leads any commission where attendance # owner is a member for pres_membership in ( parliamentarian.commission_memberships ): if (pres_membership.role == 'president' and (pres_membership.end is None or pres_membership.end >= date.today())): # Check if attendance owner is member of this # commission for member_membership in ( attendance_owner.parliamentarian .commission_memberships ): if ( member_membership.commission_id == pres_membership.commission_id and (member_membership.end is None or member_membership.end >= date.today()) ): return True return False return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=PASParliamentarian, permission=object)
[docs] def restrict_parliamentarian_access( app: PasApp, identity: Identity, model: PASParliamentarian, permission: Intent ) -> bool: # Check basic role permissions first if identity.role not in ('parliamentarian', 'commission_president'): return permission in getattr(app.settings.roles, identity.role) # For parliamentarians and commission presidents, check ownership if identity.role in ('parliamentarian', 'commission_president'): # Allow Private permission only if they have access to this record if isinstance(permission, type) and issubclass(permission, Private): user = app.session().query(User).filter_by( username=identity.userid).first() if not user or not user.parliamentarian: # type: ignore[attr-defined] return False parliamentarian: PASParliamentarian = user.parliamentarian # type: ignore[attr-defined] # Regular parliamentarians can only access their own profile if identity.role == 'parliamentarian': return model.id == parliamentarian.id # Commission presidents can access their own + commission members' elif identity.role == 'commission_president': # Always allow own profile if model.id == parliamentarian.id: return True # Check if the target parliamentarian is a member of any # commission this president leads for pres_membership in parliamentarian.commission_memberships: if (pres_membership.role == 'president' and (pres_membership.end is None or pres_membership.end >= date.today())): # Check if target parliamentarian is member of this # commission for member_membership in model.commission_memberships: if (member_membership.commission_id == pres_membership.commission_id and (member_membership.end is None or member_membership.end >= date.today())): return True return False return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=Organisation, permission=object)
[docs] def restrict_organisation_access( app: PasApp, identity: Identity, model: Organisation, permission: Intent ) -> bool: # Allow parliamentarians to access pas-settings via Organisation model return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=GeneralFileCollection, permission=object)
[docs] def restrict_files_collection_access( app: PasApp, identity: Identity, model: GeneralFileCollection, permission: Intent ) -> bool: """ Grant parliamentarians and commission presidents access to files """ # Special case: auto-grant Private access to these roles if (identity.role in ('parliamentarian', 'commission_president') and isinstance(permission, type) and issubclass(permission, Private)): return True # Default: check role permissions return permission in getattr(app.settings.roles, identity.role)
@PasApp.permission_rule(model=Commission, permission=Private)
[docs] def has_private_access_to_commission( app: PasApp, identity: Identity, model: Commission, permission: Intent ) -> bool: """ -Looks up the User from database by username - Verifies they're actually a parliamentarian - Checks their commission memberships to see if they're the - president of THIS specific commission If yes → grants access """ if identity.role == 'commission_president': user = app.session().query(User).filter_by( username=identity.userid).first() if user: if user.parliamentarian: # type: ignore[attr-defined] membershps = user.parliamentarian.commission_memberships # type: ignore[attr-defined] for membership in membershps: if membership.commission_id == model.id and \ membership.role == 'president': return True return permission in getattr(app.settings.roles, identity.role)