memory (sqla): Add columns and tables to handles permissions:
Add columns to handle `access_model` and `publish_model` and a table for `affiliations`. With those new data, components handling pubsub can now manage permissions correctly. rel 462
This commit is contained in:
parent
3dbd990902
commit
26002870a8
3 changed files with 143 additions and 4 deletions
|
|
@ -0,0 +1,40 @@
|
|||
"""add "affiliations", "access_model" and "publish_model" to PubsubNode
|
||||
|
||||
Revision ID: 8db042adb973
|
||||
Revises: 6af2d8f6be76
|
||||
Create Date: 2025-08-02 12:03:19.726497
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from libervia.backend.memory.sqla_mapping import JID
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8db042adb973'
|
||||
down_revision = '6af2d8f6be76'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('pubsub_affiliations',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('node_id', sa.Integer(), nullable=False),
|
||||
sa.Column('entity', JID(), nullable=False),
|
||||
sa.Column('affiliation', sa.Enum('outcast', 'member', 'publisher', 'owner', name='affiliation', create_constraint=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(['node_id'], ['pubsub_nodes.id'], name=op.f('fk_pubsub_affiliations_node_id_pubsub_nodes'), ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_pubsub_affiliations')),
|
||||
sa.UniqueConstraint('node_id', 'entity', name=op.f('uq_pubsub_affiliations_node_id'))
|
||||
)
|
||||
with op.batch_alter_table('pubsub_nodes', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('access_model', sa.Enum('open', 'whitelist', name='accessmodel', create_constraint=True), nullable=True))
|
||||
batch_op.add_column(sa.Column('publish_model', sa.Enum('publishers', 'subscribers', 'open', name='publishmodel', create_constraint=True), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table('pubsub_nodes', schema=None) as batch_op:
|
||||
batch_op.drop_column('publish_model')
|
||||
batch_op.drop_column('access_model')
|
||||
|
||||
op.drop_table('pubsub_affiliations')
|
||||
|
|
@ -57,6 +57,7 @@ from libervia.backend.core.log import getLogger
|
|||
from libervia.backend.memory import migration
|
||||
from libervia.backend.memory import sqla_config
|
||||
from libervia.backend.memory.sqla_mapping import (
|
||||
AccessModel,
|
||||
Base,
|
||||
Component,
|
||||
File,
|
||||
|
|
@ -74,8 +75,11 @@ from libervia.backend.memory.sqla_mapping import (
|
|||
PrivateInd,
|
||||
PrivateIndBin,
|
||||
Profile,
|
||||
PublishModel,
|
||||
PubsubAffiliation,
|
||||
PubsubItem,
|
||||
PubsubNode,
|
||||
PubsubSub,
|
||||
Subject,
|
||||
SyncState,
|
||||
Thread,
|
||||
|
|
@ -1116,7 +1120,7 @@ class Storage:
|
|||
|
||||
the older value will be retrieved from database, then update_cb will be applied to
|
||||
update it, and file will be updated checking that older value has not been changed
|
||||
meanwhile by an other user. If it has changed, it tries again a couple of times
|
||||
meanwhile by another user. If it has changed, it tries again a couple of times
|
||||
before failing
|
||||
@param column: column name (only "access" or "extra" are allowed)
|
||||
@param update_cb: method to update the value of the colum
|
||||
|
|
@ -1174,15 +1178,17 @@ class Storage:
|
|||
name: str,
|
||||
with_items: bool = False,
|
||||
with_subscriptions: bool = False,
|
||||
with_affiliations: bool = False,
|
||||
create: bool = False,
|
||||
create_kwargs: Optional[dict] = None,
|
||||
) -> Optional[PubsubNode]:
|
||||
create_kwargs: dict|None = None,
|
||||
) -> PubsubNode|None:
|
||||
"""Retrieve a PubsubNode from DB
|
||||
|
||||
@param service: service hosting the node
|
||||
@param name: node's name
|
||||
@param with_items: retrieve items in the same query
|
||||
@param with_subscriptions: retrieve subscriptions in the same query
|
||||
@param with_affiliations: retrieve affiliations in the same query
|
||||
@param create: if the node doesn't exist in DB, create it
|
||||
@param create_kwargs: keyword arguments to use with ``set_pubsub_node`` if the node
|
||||
needs to be created.
|
||||
|
|
@ -1197,6 +1203,8 @@ class Storage:
|
|||
stmt = stmt.options(joinedload(PubsubNode.items))
|
||||
if with_subscriptions:
|
||||
stmt = stmt.options(joinedload(PubsubNode.subscriptions))
|
||||
if with_affiliations:
|
||||
stmt = stmt.options(joinedload(PubsubNode.affiliations))
|
||||
result = await session.execute(stmt)
|
||||
ret = result.unique().scalar_one_or_none()
|
||||
if ret is None and create:
|
||||
|
|
@ -1219,6 +1227,7 @@ class Storage:
|
|||
name,
|
||||
with_items=with_items,
|
||||
with_subscriptions=with_subscriptions,
|
||||
with_affiliations=with_affiliations,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
|
@ -1226,26 +1235,60 @@ class Storage:
|
|||
else:
|
||||
return ret
|
||||
|
||||
@aio
|
||||
async def get_pubsub_nodes(
|
||||
self,
|
||||
client: SatXMPPEntity|None,
|
||||
service: jid.JID|None
|
||||
) -> list[PubsubNode]:
|
||||
"""Retrieve pubsub nodes matching arguments.
|
||||
|
||||
@param client: If set, only return nodes of this client profile.
|
||||
@param service: If set, only return nodes from this service.
|
||||
@return: List of matching pubsub nodes.
|
||||
"""
|
||||
async with self.session() as session:
|
||||
stm = select(PubsubNode)
|
||||
if client is not None:
|
||||
profile_id = self.profiles[client.profile]
|
||||
stm = stm.where(PubsubNode.profile_id == profile_id)
|
||||
if service is not None:
|
||||
stm = stm.where(PubsubNode.service == service)
|
||||
result = await session.execute(stm)
|
||||
|
||||
return result.scalars().all()
|
||||
|
||||
@aio
|
||||
async def set_pubsub_node(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
service: jid.JID,
|
||||
name: str,
|
||||
access_model: AccessModel|None = None,
|
||||
publish_model: PublishModel|None = None,
|
||||
analyser: Optional[str] = None,
|
||||
type_: Optional[str] = None,
|
||||
subtype: Optional[str] = None,
|
||||
subscribed: bool = False,
|
||||
extra: dict|None = None,
|
||||
items: list[PubsubItem]|None = None,
|
||||
affiliations: list[PubsubAffiliation]|None = None,
|
||||
subscriptions: list[PubsubSub]|None = None,
|
||||
) -> PubsubNode:
|
||||
node = PubsubNode(
|
||||
profile_id=self.profiles[client.profile],
|
||||
service=service,
|
||||
name=name,
|
||||
access_model=access_model,
|
||||
publish_model=publish_model,
|
||||
subscribed=subscribed,
|
||||
analyser=analyser,
|
||||
type_=type_,
|
||||
subtype=subtype,
|
||||
subscriptions=[],
|
||||
extra = extra,
|
||||
items = items or [],
|
||||
affiliations = affiliations or [],
|
||||
subscriptions = subscriptions or []
|
||||
)
|
||||
async with self.session() as session:
|
||||
async with session.begin():
|
||||
|
|
|
|||
|
|
@ -105,11 +105,29 @@ class SyncState(enum.Enum):
|
|||
NO_SYNC = 4
|
||||
|
||||
|
||||
class AccessModel(enum.StrEnum):
|
||||
open = enum.auto()
|
||||
whitelist = enum.auto()
|
||||
|
||||
|
||||
class PublishModel(enum.StrEnum):
|
||||
publishers = enum.auto()
|
||||
subscribers = enum.auto()
|
||||
open = enum.auto()
|
||||
|
||||
|
||||
class SubscriptionState(enum.Enum):
|
||||
SUBSCRIBED = 1
|
||||
PENDING = 2
|
||||
|
||||
|
||||
class Affiliation(enum.StrEnum):
|
||||
outcast = enum.auto()
|
||||
member = enum.auto()
|
||||
publisher = enum.auto()
|
||||
owner = enum.auto()
|
||||
|
||||
|
||||
class NotificationType(enum.Enum):
|
||||
chat = "chat"
|
||||
blog = "blog"
|
||||
|
|
@ -604,6 +622,20 @@ class PubsubNode(Base):
|
|||
profile_id = Column(ForeignKey("profiles.id", ondelete="CASCADE"))
|
||||
service = Column(JID)
|
||||
name = Column(Text, nullable=False)
|
||||
access_model = Column(
|
||||
Enum(
|
||||
AccessModel,
|
||||
create_constraint=True,
|
||||
),
|
||||
nullable=True,
|
||||
)
|
||||
publish_model = Column(
|
||||
Enum(
|
||||
PublishModel,
|
||||
create_constraint=True,
|
||||
),
|
||||
nullable=True,
|
||||
)
|
||||
subscribed = Column(
|
||||
Boolean(create_constraint=True, name="subscribed_bool"), nullable=False
|
||||
)
|
||||
|
|
@ -622,12 +654,36 @@ class PubsubNode(Base):
|
|||
extra = Column(JSON)
|
||||
|
||||
items = relationship("PubsubItem", back_populates="node", passive_deletes=True)
|
||||
affiliations = relationship("PubsubAffiliation", back_populates="node", passive_deletes=True)
|
||||
subscriptions = relationship("PubsubSub", back_populates="node", passive_deletes=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Pubsub node {self.name!r} at {self.service}"
|
||||
|
||||
|
||||
class PubsubAffiliation(Base):
|
||||
"""Affiliations to pubsub nodes.
|
||||
|
||||
User by components managing a pubsub service.
|
||||
"""
|
||||
__tablename__ = "pubsub_affiliations"
|
||||
__table_args__ = (UniqueConstraint("node_id", "entity"),)
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
node_id = Column(ForeignKey("pubsub_nodes.id", ondelete="CASCADE"), nullable=False)
|
||||
entity = Column(JID, nullable=False)
|
||||
affiliation = Column(
|
||||
Enum(
|
||||
Affiliation,
|
||||
create_constraint=True,
|
||||
),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
node = relationship("PubsubNode", back_populates="affiliations")
|
||||
|
||||
|
||||
class PubsubSub(Base):
|
||||
"""Subscriptions to pubsub nodes
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue