from __future__ import annotations
from onegov.core.orm import observes, Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.user import UserGroup
from sqlalchemy import CheckConstraint, ForeignKey
from sqlalchemy.orm import mapped_column, object_session, relationship, Mapped
from uuid import uuid4, UUID
[docs]
class TicketPermission(Base, TimestampMixin):
""" Defines a custom ticket permission.
If a ticket permission is defined for ticket handler (and optionally a
group), a user has to be in the given user group to access these tickets.
"""
[docs]
__tablename__ = 'ticket_permissions'
#: the id
[docs]
id: Mapped[UUID] = mapped_column(
primary_key=True,
default=uuid4
)
#: the user group needed for accessing the tickets
[docs]
user_group_id: Mapped[UUID] = mapped_column(ForeignKey(UserGroup.id))
[docs]
user_group: Mapped[UserGroup] = relationship(
back_populates='ticket_permissions'
)
#: the handler code this permission addresses
[docs]
handler_code: Mapped[str]
#: the group this permission addresses
[docs]
group: Mapped[str | None]
#: whether or not this permission is exclusive
#: if a permission is exclusive, the same permission may not
#: be given non-exclusively to another group, but multiple groups
#: may have the same exclusive or non-exclusive permission
[docs]
exclusive: Mapped[bool] = mapped_column(
default=True,
index=True
)
#: whether or not to immediately send notifications about new tickets
[docs]
__table_args__ = (
CheckConstraint(
exclusive.is_not_distinct_from(True)
| immediate_notification.is_not_distinct_from(True),
name='no_redundant_ticket_permissions'
),
)
# NOTE: A unique constraint doesn't work here, since group is nullable
@observes('handler_code', 'group')
[docs]
def ensure_uniqueness(
self,
handler_code: str,
group: str | None
) -> None:
# this should always be set
assert self.handler_code
if not (user_group_id := (
self.user_group_id
or (self.user_group and self.user_group.id)
)):
# this is an incomplete record that should fail in
# a different way
return
session = object_session(self)
assert session is not None
query = session.query(TicketPermission)
# we can't conflict with ourselves, only with other permissions
if self.id is not None:
query = query.filter(
TicketPermission.id != self.id
)
# this defines whether or not two permissions target the same tickets
query = query.filter(
TicketPermission.handler_code == self.handler_code
)
query = query.filter(
TicketPermission.group.is_not_distinct_from(self.group)
)
# the exact same permission may only exist once per user group
constraint_violated = query.filter(
TicketPermission.user_group_id == user_group_id
).exists()
if session.query(constraint_violated).scalar():
raise ValueError('Uniqueness violation in ticket permissions')