Use an ACL table to check for privileges

This commit is contained in:
grossmj committed 2023-08-27 18:20:42 +10:00
1 parent 6bd855b3c5
commit 60ce1172e0
30 files changed
+1195 -1423

No files matched your search

+2 -3
View File
@@ -16,11 +16,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .base import Base
from .acl import ACL
from .resources import Resource
from .acl import ACE
from .users import User, UserGroup
from .roles import Role
from .permissions import Permission
from .privileges import Privilege
from .computes import Compute
from .images import Image
from .templates import (
+10 -5
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Column, Boolean, ForeignKey
from sqlalchemy import Column, String, Boolean, ForeignKey, CheckConstraint
from sqlalchemy.orm import relationship
from .base import BaseTable, generate_uuid, GUID
@@ -25,17 +25,22 @@ import logging
log = logging.getLogger(__name__)
class ACL(BaseTable):
class ACE(BaseTable):
__tablename__ = "acl"
acl_id = Column(GUID, primary_key=True, default=generate_uuid)
ace_id = Column(GUID, primary_key=True, default=generate_uuid)
path = Column(String)
propagate = Column(Boolean, default=True)
allowed = Column(Boolean, default=True)
type: str = Column(String)
user_id = Column(GUID, ForeignKey('users.user_id', ondelete="CASCADE"))
user = relationship("User", back_populates="acl_entries")
group_id = Column(GUID, ForeignKey('user_groups.user_group_id', ondelete="CASCADE"))
group = relationship("UserGroup", back_populates="acl_entries")
resource_id = Column(GUID, ForeignKey('resources.resource_id', ondelete="CASCADE"))
resource = relationship("Resource", back_populates="acl_entries")
role_id = Column(GUID, ForeignKey('roles.role_id', ondelete="CASCADE"))
role = relationship("Role", back_populates="acl_entries")
__table_args__ = (
CheckConstraint("(user_id IS NOT NULL AND type = 'user') OR (group_id IS NOT NULL AND type = 'group')"),
)
-129
View File
@@ -1,129 +0,0 @@
#!/usr/bin/env python
#
# Copyright (C) 2021 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Table, Column, String, ForeignKey, event
from sqlalchemy.orm import relationship
from .base import Base, BaseTable, generate_uuid, GUID, ListType
import logging
log = logging.getLogger(__name__)
permission_role_map = Table(
"permission_role_map",
Base.metadata,
Column("permission_id", GUID, ForeignKey("permissions.permission_id", ondelete="CASCADE")),
Column("role_id", GUID, ForeignKey("roles.role_id", ondelete="CASCADE"))
)
class Permission(BaseTable):
__tablename__ = "permissions"
permission_id = Column(GUID, primary_key=True, default=generate_uuid)
description = Column(String)
methods = Column(ListType)
path = Column(String)
action = Column(String)
user_id = Column(GUID, ForeignKey('users.user_id', ondelete="CASCADE"))
user = relationship("User", back_populates="permissions")
roles = relationship("Role", secondary=permission_role_map, back_populates="permissions")
@event.listens_for(Permission.__table__, 'after_create')
def create_default_roles(target, connection, **kw):
default_permissions = [
{
"description": "Allow access to all endpoints",
"methods": ["GET", "POST", "PUT", "DELETE"],
"path": "/",
"action": "ALLOW"
},
{
"description": "Allow to receive controller notifications",
"methods": ["GET"],
"path": "/notifications",
"action": "ALLOW"
},
{
"description": "Allow to create and list projects",
"methods": ["GET", "POST"],
"path": "/projects",
"action": "ALLOW"
},
{
"description": "Allow to create and list templates",
"methods": ["GET", "POST"],
"path": "/templates",
"action": "ALLOW"
},
{
"description": "Allow to list computes",
"methods": ["GET"],
"path": "/computes/*",
"action": "ALLOW"
},
{
"description": "Allow access to all symbol endpoints",
"methods": ["GET", "POST"],
"path": "/symbols/*",
"action": "ALLOW"
},
]
stmt = target.insert().values(default_permissions)
connection.execute(stmt)
connection.commit()
log.debug("The default permissions have been created in the database")
@event.listens_for(permission_role_map, 'after_create')
def add_permissions_to_role(target, connection, **kw):
from .roles import Role
roles_table = Role.__table__
stmt = roles_table.select().where(roles_table.c.name == "Administrator")
result = connection.execute(stmt)
role_id = result.first().role_id
permissions_table = Permission.__table__
stmt = permissions_table.select().where(permissions_table.c.path == "/")
result = connection.execute(stmt)
permission_id = result.first().permission_id
# add root path to the "Administrator" role
stmt = target.insert().values(permission_id=permission_id, role_id=role_id)
connection.execute(stmt)
stmt = roles_table.select().where(roles_table.c.name == "User")
result = connection.execute(stmt)
role_id = result.first().role_id
# add minimum required paths to the "User" role
for path in ("/notifications", "/projects", "/templates", "/computes/*", "/symbols/*"):
stmt = permissions_table.select().where(permissions_table.c.path == path)
result = connection.execute(stmt)
permission_id = result.first().permission_id
stmt = target.insert().values(permission_id=permission_id, role_id=role_id)
connection.execute(stmt)
connection.commit()
+258
View File
@@ -0,0 +1,258 @@
#!/usr/bin/env python
#
# Copyright (C) 2023 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Table, Column, String, ForeignKey, event
from sqlalchemy.orm import relationship
from .base import Base, BaseTable, generate_uuid, GUID
import logging
log = logging.getLogger(__name__)
privilege_role_map = Table(
"privilege_role_map",
Base.metadata,
Column("privilege_id", GUID, ForeignKey("privileges.privilege_id", ondelete="CASCADE")),
Column("role_id", GUID, ForeignKey("roles.role_id", ondelete="CASCADE"))
)
class Privilege(BaseTable):
__tablename__ = "privileges"
privilege_id = Column(GUID, primary_key=True, default=generate_uuid)
name = Column(String)
description = Column(String)
roles = relationship("Role", secondary=privilege_role_map, back_populates="privileges")
@event.listens_for(Privilege.__table__, 'after_create')
def create_default_roles(target, connection, **kw):
default_privileges = [
{
"description": "Create or delete a user",
"name": "User.Allocate"
},
{
"description": "View a user",
"name": "User.Audit"
},
{
"description": "Update a user",
"name": "User.Modify"
},
{
"description": "Create or delete a group",
"name": "Group.Allocate"
},
{
"description": "View a group",
"name": "Group.Audit"
},
{
"description": "Update a group",
"name": "Group.Modify"
},
{
"description": "Create or delete a template",
"name": "Template.Allocate"
},
{
"description": "View a template",
"name": "Template.Audit"
},
{
"description": "Update a template",
"name": "Template.Modify"
},
{
"description": "Create or delete a project",
"name": "Project.Allocate"
},
{
"description": "View a project",
"name": "Project.Audit"
},
{
"description": "Update a project",
"name": "Project.Modify"
},
{
"description": "Create or delete project snapshots",
"name": "Project.Snapshot"
},
{
"description": "Create or delete a node",
"name": "Node.Allocate"
},
{
"description": "View a node",
"name": "Node.Audit"
},
{
"description": "Update a node",
"name": "Node.Modify"
},
{
"description": "Console access to a node",
"name": "Node.Console"
},
{
"description": "Power management for a node",
"name": "Node.PowerMgmt"
},
{
"description": "Create or delete a link",
"name": "Link.Allocate"
},
{
"description": "View a link",
"name": "Link.Audit"
},
{
"description": "Update a link",
"name": "Link.Modify"
},
{
"description": "Capture packets on a link",
"name": "Link.Capture"
},
{
"description": "Create or delete a drawing",
"name": "Drawing.Allocate"
},
{
"description": "View a drawing",
"name": "Drawing.Audit"
},
{
"description": "Update a drawing",
"name": "Drawing.Modify"
},
{
"description": "Create or delete a symbol",
"name": "Symbol.Allocate"
},
{
"description": "View a symbol",
"name": "Symbol.Audit"
},
{
"description": "Create or delete an image",
"name": "Image.Allocate"
},
{
"description": "View an image",
"name": "Image.Audit"
},
{
"description": "Create or delete a compute",
"name": "Compute.Allocate"
},
{
"description": "View a compute",
"name": "Compute.Audit"
},
]
stmt = target.insert().values(default_privileges)
connection.execute(stmt)
connection.commit()
log.debug("The default privileges have been created in the database")
def add_privileges_to_role(target, connection, role, privileges):
from .roles import Role
roles_table = Role.__table__
privileges_table = Privilege.__table__
stmt = roles_table.select().where(roles_table.c.name == role)
result = connection.execute(stmt)
role_id = result.first().role_id
for privilege_name in privileges:
stmt = privileges_table.select().where(privileges_table.c.name == privilege_name)
result = connection.execute(stmt)
privilege_id = result.first().privilege_id
stmt = target.insert().values(privilege_id=privilege_id, role_id=role_id)
connection.execute(stmt)
@event.listens_for(privilege_role_map, 'after_create')
def add_privileges_to_default_roles(target, connection, **kw):
from .roles import Role
roles_table = Role.__table__
stmt = roles_table.select().where(roles_table.c.name == "Administrator")
result = connection.execute(stmt)
role_id = result.first().role_id
# add all privileges to the "Administrator" role
privileges_table = Privilege.__table__
stmt = privileges_table.select()
result = connection.execute(stmt)
for row in result:
privilege_id = row.privilege_id
stmt = target.insert().values(privilege_id=privilege_id, role_id=role_id)
connection.execute(stmt)
# add required privileges to the "User" role
user_privileges = (
"Project.Allocate",
"Project.Audit",
"Project.Modify",
"Project.Snapshot",
"Node.Allocate",
"Node.Audit",
"Node.Modify",
"Node.Console",
"Node.PowerMgmt",
"Link.Allocate",
"Link.Audit",
"Link.Modify",
"Link.Capture",
"Drawing.Allocate",
"Drawing.Audit",
"Drawing.Modify",
"Template.Audit",
"Symbol.Audit",
"Image.Audit",
"Compute.Audit"
)
add_privileges_to_role(target, connection, "User", user_privileges)
# add required privileges to the "Auditor" role
auditor_privileges = (
"Project.Audit",
"Node.Audit",
"Link.Audit",
"Drawing.Audit",
"Template.Audit",
"Symbol.Audit",
"Image.Audit",
"Compute.Audit"
)
add_privileges_to_role(target, connection, "Auditor", auditor_privileges)
connection.commit()
log.debug("Privileges have been added to the default roles in the database")
@@ -25,21 +25,18 @@ import logging
log = logging.getLogger(__name__)
class Resource(BaseTable):
class ResourcePool(BaseTable):
__tablename__ = "resources"
__tablename__ = "resource_pools"
resource_id = Column(GUID, primary_key=True, default=generate_uuid)
name = Column(String, unique=True, index=True)
description = Column(String)
propagate = Column(Boolean, default=True)
user_id = Column(GUID, ForeignKey('users.user_id', ondelete="CASCADE"))
user = relationship("User", back_populates="resources")
acl_entries = relationship("ACL")
parent_id = Column(GUID, ForeignKey("resources.resource_id", ondelete="CASCADE"))
children = relationship(
"Resource",
remote_side=[resource_id],
cascade="all, delete-orphan",
single_parent=True
)
resource_id = Column(GUID, primary_key=True)
resource_type = Column(String)
# # Create a self-referential relationship to represent a hierarchy of resources
# parent_id = Column(GUID, ForeignKey("resources.resource_id", ondelete="CASCADE"))
# children = relationship(
# "Resource",
# remote_side=[resource_id],
# cascade="all, delete-orphan",
# single_parent=True
# )
+7 -35
View File
@@ -15,23 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Table, Column, String, Boolean, ForeignKey, event
from sqlalchemy import Column, String, Boolean, event
from sqlalchemy.orm import relationship
from .base import Base, BaseTable, generate_uuid, GUID
from .permissions import permission_role_map
from .base import BaseTable, generate_uuid, GUID
from .privileges import privilege_role_map
import logging
log = logging.getLogger(__name__)
role_group_map = Table(
"role_group_map",
Base.metadata,
Column("role_id", GUID, ForeignKey("roles.role_id", ondelete="CASCADE")),
Column("user_group_id", GUID, ForeignKey("user_groups.user_group_id", ondelete="CASCADE"))
)
class Role(BaseTable):
@@ -41,9 +34,8 @@ class Role(BaseTable):
name = Column(String, unique=True, index=True)
description = Column(String)
is_builtin = Column(Boolean, default=False)
permissions = relationship("Permission", secondary=permission_role_map, back_populates="roles")
groups = relationship("UserGroup", secondary=role_group_map, back_populates="roles")
acl_entries = relationship("ACL")
privileges = relationship("Privilege", secondary=privilege_role_map, back_populates="roles")
acl_entries = relationship("ACE")
@event.listens_for(Role.__table__, 'after_create')
@@ -52,31 +44,11 @@ def create_default_roles(target, connection, **kw):
default_roles = [
{"name": "Administrator", "description": "Administrator role", "is_builtin": True},
{"name": "User", "description": "User role", "is_builtin": True},
{"name": "Auditor", "description": "Role with read only access", "is_builtin": True},
{"name": "No Access", "description": "Role with no privileges (used to forbid access)", "is_builtin": True}
]
stmt = target.insert().values(default_roles)
connection.execute(stmt)
connection.commit()
log.debug("The default roles have been created in the database")
@event.listens_for(role_group_map, 'after_create')
def add_admin_to_group(target, connection, **kw):
from .users import UserGroup
user_groups_table = UserGroup.__table__
roles_table = Role.__table__
# Add roles to built-in user groups
groups_to_roles = {"Administrators": "Administrator", "Users": "User"}
for user_group, role in groups_to_roles.items():
stmt = user_groups_table.select().where(user_groups_table.c.name == user_group)
result = connection.execute(stmt)
user_group_id = result.first().user_group_id
stmt = roles_table.select().where(roles_table.c.name == role)
result = connection.execute(stmt)
role_id = result.first().role_id
stmt = target.insert().values(role_id=role_id, user_group_id=user_group_id)
connection.execute(stmt)
connection.commit()
+2 -24
View File
@@ -19,7 +19,6 @@ from sqlalchemy import Table, Boolean, Column, String, DateTime, ForeignKey, eve
from sqlalchemy.orm import relationship
from .base import Base, BaseTable, generate_uuid, GUID
from .roles import role_group_map
from gns3server.config import Config
from gns3server.services import auth_service
@@ -49,9 +48,7 @@ class User(BaseTable):
is_active = Column(Boolean, default=True)
is_superadmin = Column(Boolean, default=False)
groups = relationship("UserGroup", secondary=user_group_map, back_populates="users")
resources = relationship("Resource")
permissions = relationship("Permission")
acl_entries = relationship("ACL")
acl_entries = relationship("ACE")
@event.listens_for(User.__table__, 'after_create')
@@ -80,8 +77,7 @@ class UserGroup(BaseTable):
name = Column(String, unique=True, index=True)
is_builtin = Column(Boolean, default=False)
users = relationship("User", secondary=user_group_map, back_populates="groups")
roles = relationship("Role", secondary=role_group_map, back_populates="groups")
acl_entries = relationship("ACL")
acl_entries = relationship("ACE")
@event.listens_for(UserGroup.__table__, 'after_create')
@@ -96,21 +92,3 @@ def create_default_user_groups(target, connection, **kw):
connection.execute(stmt)
connection.commit()
log.debug("The default user groups have been created in the database")
# @event.listens_for(user_group_link, 'after_create')
# def add_admin_to_group(target, connection, **kw):
#
# user_groups_table = UserGroup.__table__
# stmt = user_groups_table.select().where(user_groups_table.c.name == "Administrators")
# result = connection.execute(stmt)
# user_group_id = result.first().user_group_id
#
# users_table = User.__table__
# stmt = users_table.select().where(users_table.c.is_superadmin.is_(True))
# result = connection.execute(stmt)
# user_id = result.first().user_id
#
# stmt = target.insert().values(user_id=user_id, user_group_id=user_group_id)
# connection.execute(stmt)
# connection.commit()
+158 -191
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
# Copyright (C) 2023 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@ from sqlalchemy.orm import selectinload
from .base import BaseRepository
import gns3server.db.models as models
from gns3server.schemas.controller.rbac import HTTPMethods, PermissionAction
from gns3server import schemas
import logging
@@ -44,7 +43,7 @@ class RbacRepository(BaseRepository):
"""
query = select(models.Role).\
options(selectinload(models.Role.permissions)).\
options(selectinload(models.Role.privileges)).\
where(models.Role.role_id == role_id)
result = await self._db_session.execute(query)
return result.scalars().first()
@@ -55,9 +54,8 @@ class RbacRepository(BaseRepository):
"""
query = select(models.Role).\
options(selectinload(models.Role.permissions)).\
options(selectinload(models.Role.privileges)).\
where(models.Role.name == name)
#query = select(models.Role).where(models.Role.name == name)
result = await self._db_session.execute(query)
return result.scalars().first()
@@ -66,7 +64,7 @@ class RbacRepository(BaseRepository):
Get all roles.
"""
query = select(models.Role).options(selectinload(models.Role.permissions))
query = select(models.Role).options(selectinload(models.Role.privileges))
result = await self._db_session.execute(query)
return result.scalars().all()
@@ -81,7 +79,6 @@ class RbacRepository(BaseRepository):
)
self._db_session.add(db_role)
await self._db_session.commit()
#await self._db_session.refresh(db_role)
return await self.get_role(db_role.role_id)
async def update_role(
@@ -115,286 +112,256 @@ class RbacRepository(BaseRepository):
await self._db_session.commit()
return result.rowcount > 0
async def add_permission_to_role(
async def add_privilege_to_role(
self,
role_id: UUID,
permission: models.Permission
privilege: models.Privilege
) -> Union[None, models.Role]:
"""
Add a permission to a role.
Add a privilege to a role.
"""
query = select(models.Role).\
options(selectinload(models.Role.permissions)).\
options(selectinload(models.Role.privileges)).\
where(models.Role.role_id == role_id)
result = await self._db_session.execute(query)
role_db = result.scalars().first()
if not role_db:
return None
role_db.permissions.append(permission)
role_db.privileges.append(privilege)
await self._db_session.commit()
await self._db_session.refresh(role_db)
return role_db
async def remove_permission_from_role(
async def remove_privilege_from_role(
self,
role_id: UUID,
permission: models.Permission
privilege: models.Privilege
) -> Union[None, models.Role]:
"""
Remove a permission from a role.
Remove a privilege from a role.
"""
query = select(models.Role).\
options(selectinload(models.Role.permissions)).\
options(selectinload(models.Role.privileges)).\
where(models.Role.role_id == role_id)
result = await self._db_session.execute(query)
role_db = result.scalars().first()
if not role_db:
return None
role_db.permissions.remove(permission)
role_db.privileges.remove(privilege)
await self._db_session.commit()
await self._db_session.refresh(role_db)
return role_db
async def get_role_permissions(self, role_id: UUID) -> List[models.Permission]:
async def get_role_privileges(self, role_id: UUID) -> List[models.Privilege]:
"""
Get all the role permissions.
Get all the role privileges.
"""
query = select(models.Permission).\
join(models.Permission.roles).\
query = select(models.Privilege).\
join(models.Privilege.roles).\
filter(models.Role.role_id == role_id)
result = await self._db_session.execute(query)
return result.scalars().all()
async def get_permission(self, permission_id: UUID) -> Optional[models.Permission]:
async def get_privilege(self, privilege_id: UUID) -> Optional[models.Privilege]:
"""
Get a permission by its ID.
Get a privilege by its ID.
"""
query = select(models.Permission).where(models.Permission.permission_id == permission_id)
query = select(models.Privilege).where(models.Privilege.privilege_id == privilege_id)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_permission_by_path(self, path: str) -> Optional[models.Permission]:
async def get_privilege_by_name(self, name: str) -> Optional[models.Privilege]:
"""
Get a permission by its path.
Get a privilege by its name.
"""
query = select(models.Permission).where(models.Permission.path == path)
query = select(models.Privilege).where(models.Privilege.name == name)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_permissions(self) -> List[models.Permission]:
async def get_privileges(self) -> List[models.Privilege]:
"""
Get all permissions.
Get all privileges.
"""
query = select(models.Permission).\
order_by(models.Permission.path.desc())
query = select(models.Privilege)
result = await self._db_session.execute(query)
return result.scalars().all()
async def check_permission_exists(self, permission_create: schemas.PermissionCreate) -> bool:
async def get_ace(self, ace_id: UUID) -> Optional[models.ACE]:
"""
Check if a permission exists.
Get an ACE by its ID.
"""
query = select(models.Permission).\
where(models.Permission.methods == permission_create.methods,
models.Permission.path == permission_create.path,
models.Permission.action == permission_create.action)
query = select(models.ACE).where(models.ACE.ace_id == ace_id)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_ace_by_path(self, path: str) -> Optional[models.ACE]:
"""
Get an ACE by its path.
"""
query = select(models.ACE).where(models.ACE.path == path)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_aces(self) -> List[models.ACE]:
"""
Get all ACEs.
"""
query = select(models.ACE)
result = await self._db_session.execute(query)
return result.scalars().all()
async def check_ace_exists(self, path: str) -> bool:
"""
Check if an ACE exists.
"""
query = select(models.ACE).\
where(models.ACE.path == path)
result = await self._db_session.execute(query)
return result.scalars().first() is not None
async def create_permission(self, permission_create: schemas.PermissionCreate) -> models.Permission:
async def create_ace(self, ace_create: schemas.ACECreate) -> models.ACE:
"""
Create a new permission.
Create a new ACE
"""
db_permission = models.Permission(
description=permission_create.description,
methods=permission_create.methods,
path=permission_create.path,
action=permission_create.action,
)
self._db_session.add(db_permission)
create_values = ace_create.model_dump(exclude_unset=True)
db_ace = models.ACE(**create_values)
self._db_session.add(db_ace)
await self._db_session.commit()
await self._db_session.refresh(db_permission)
return db_permission
await self._db_session.refresh(db_ace)
return db_ace
async def update_permission(
async def update_ace(
self,
permission_id: UUID,
permission_update: schemas.PermissionUpdate
) -> Optional[models.Permission]:
ace_id: UUID,
ace_update: schemas.ACEUpdate
) -> Optional[models.ACE]:
"""
Update a permission.
Update an ACE
"""
update_values = permission_update.model_dump(exclude_unset=True)
query = update(models.Permission).\
where(models.Permission.permission_id == permission_id).\
update_values = ace_update.model_dump(exclude_unset=True)
query = update(models.ACE).\
where(models.ACE.ace_id == ace_id).\
values(update_values)
await self._db_session.execute(query)
await self._db_session.commit()
permission_db = await self.get_permission(permission_id)
if permission_db:
await self._db_session.refresh(permission_db) # force refresh of updated_at value
return permission_db
ace_db = await self.get_ace(ace_id)
if ace_db:
await self._db_session.refresh(ace_db) # force refresh of updated_at value
return ace_db
async def delete_permission(self, permission_id: UUID) -> bool:
async def delete_ace(self, ace_id: UUID) -> bool:
"""
Delete a permission.
Delete an ACE
"""
query = delete(models.Permission).where(models.Permission.permission_id == permission_id)
query = delete(models.ACE).where(models.ACE.ace_id == ace_id)
result = await self._db_session.execute(query)
await self._db_session.commit()
return result.rowcount > 0
async def prune_permissions(self) -> int:
# async def prune_permissions(self) -> int:
# """
# Prune orphaned permissions.
# """
#
# query = select(models.Permission).\
# filter((~models.Permission.roles.any()) & (models.Permission.user_id == null()))
# result = await self._db_session.execute(query)
# permissions = result.scalars().all()
# permissions_deleted = 0
# for permission in permissions:
# if await self.delete_permission(permission.permission_id):
# permissions_deleted += 1
# log.info(f"{permissions_deleted} orphaned permissions have been deleted")
# return permissions_deleted
#
# def _match_permission(
# self,
# permissions: List[models.Permission],
# method: str,
# path: str
# ) -> Union[None, models.Permission]:
# """
# Match the methods and path with a permission.
# """
#
# for permission in permissions:
# log.debug(f"RBAC: checking permission {permission.methods} {permission.path} {permission.action}")
# if method not in permission.methods:
# continue
# if permission.path.endswith("/*") and path.startswith(permission.path[:-2]):
# return permission
# elif permission.path == path:
# return permission
async def delete_all_ace_starting_with_path(self, path: str) -> None:
"""
Prune orphaned permissions.
Delete all ACEs starting with path.
"""
query = select(models.Permission).\
filter((~models.Permission.roles.any()) & (models.Permission.user_id == null()))
result = await self._db_session.execute(query)
permissions = result.scalars().all()
permissions_deleted = 0
for permission in permissions:
if await self.delete_permission(permission.permission_id):
permissions_deleted += 1
log.info(f"{permissions_deleted} orphaned permissions have been deleted")
return permissions_deleted
def _match_permission(
self,
permissions: List[models.Permission],
method: str,
path: str
) -> Union[None, models.Permission]:
"""
Match the methods and path with a permission.
"""
for permission in permissions:
log.debug(f"RBAC: checking permission {permission.methods} {permission.path} {permission.action}")
if method not in permission.methods:
continue
if permission.path.endswith("/*") and path.startswith(permission.path[:-2]):
return permission
elif permission.path == path:
return permission
async def get_user_permissions(self, user_id: UUID):
"""
Get all permissions from an user.
"""
query = select(models.Permission).\
join(models.User.permissions).\
filter(models.User.user_id == user_id).\
order_by(models.Permission.path.desc())
result = await self._db_session.execute(query)
return result.scalars().all()
async def add_permission_to_user(
self,
user_id: UUID,
permission: models.Permission
) -> Union[None, models.User]:
"""
Add a permission to an user.
"""
query = select(models.User).\
options(selectinload(models.User.permissions)).\
where(models.User.user_id == user_id)
result = await self._db_session.execute(query)
user_db = result.scalars().first()
if not user_db:
return None
user_db.permissions.append(permission)
await self._db_session.commit()
await self._db_session.refresh(user_db)
return user_db
async def remove_permission_from_user(
self,
user_id: UUID,
permission: models.Permission
) -> Union[None, models.User]:
"""
Remove a permission from a role.
"""
query = select(models.User).\
options(selectinload(models.User.permissions)).\
where(models.User.user_id == user_id)
result = await self._db_session.execute(query)
user_db = result.scalars().first()
if not user_db:
return None
user_db.permissions.remove(permission)
await self._db_session.commit()
await self._db_session.refresh(user_db)
return user_db
async def add_permission_to_user_with_path(self, user_id: UUID, path: str) -> Union[None, models.User]:
"""
Add a permission to an user.
"""
# Create a new permission with full rights on path
new_permission = schemas.PermissionCreate(
description=f"Allow access to {path}",
methods=[HTTPMethods.get, HTTPMethods.head, HTTPMethods.post, HTTPMethods.put, HTTPMethods.delete],
path=path,
action=PermissionAction.allow
)
permission_db = await self.create_permission(new_permission)
# Add the permission to the user
query = select(models.User).\
options(selectinload(models.User.permissions)).\
where(models.User.user_id == user_id)
result = await self._db_session.execute(query)
user_db = result.scalars().first()
if not user_db:
return None
user_db.permissions.append(permission_db)
await self._db_session.commit()
await self._db_session.refresh(user_db)
return user_db
async def delete_all_permissions_with_path(self, path: str) -> None:
"""
Delete all permissions with path.
"""
query = delete(models.Permission).\
where(models.Permission.path.startswith(path)).\
query = delete(models.ACE).\
where(models.ACE.path.startswith(path)).\
execution_options(synchronize_session=False)
result = await self._db_session.execute(query)
log.debug(f"{result.rowcount} permission(s) have been deleted")
log.debug(f"{result.rowcount} ACE(s) have been deleted")
async def check_user_is_authorized(self, user_id: UUID, method: str, path: str) -> bool:
async def check_user_has_privilege(self, user_id: UUID, path: str, privilege_name: str) -> bool:
# query = select(models.Privilege.name).\
# join(models.Privilege.roles).\
# join(models.Role.acl_entries).\
# join(models.ACE.user).\
# filter(models.Privilege.name == privilege). \
# filter(models.User.user_id == user_id).\
# filter(models.ACE.path == path).\
# distinct()
#query = select(models.ACE.path)
#result = await self._db_session.execute(query)
#res = result.scalars().all()
#print("ACL TABLE ==>", res)
#for ace in res:
# print(ace)
query = select(models.Privilege.name, models.ACE.path, models.ACE.propagate).\
join(models.Privilege.roles).\
join(models.Role.acl_entries).\
join(models.ACE.user).\
filter(models.User.user_id == user_id).\
filter(models.Privilege.name == privilege_name).\
filter(models.ACE.path == path).\
order_by(models.ACE.path.desc())
result = await self._db_session.execute(query)
privileges = result.all()
#print(privileges)
for privilege, privilege_path, propagate in privileges:
if privilege_path == path:
return True
return False
async def check_user_is_authorized(self, user_id: UUID, path: str) -> bool:
"""
Check if an user is authorized to access a resource.
Check if a user is authorized to access a resource.
"""
return True
query = select(models.Permission).\
join(models.Permission.roles).\
join(models.Role.groups).\
-57
View File
@@ -287,60 +287,3 @@ class UsersRepository(BaseRepository):
result = await self._db_session.execute(query)
return result.scalars().all()
async def add_role_to_user_group(
self,
user_group_id: UUID,
role: models.Role
) -> Union[None, models.UserGroup]:
"""
Add a role to a user group.
"""
query = select(models.UserGroup).\
options(selectinload(models.UserGroup.roles)).\
where(models.UserGroup.user_group_id == user_group_id)
result = await self._db_session.execute(query)
user_group_db = result.scalars().first()
if not user_group_db:
return None
user_group_db.roles.append(role)
await self._db_session.commit()
await self._db_session.refresh(user_group_db)
return user_group_db
async def remove_role_from_user_group(
self,
user_group_id: UUID,
role: models.Role
) -> Union[None, models.UserGroup]:
"""
Remove a role from a user group.
"""
query = select(models.UserGroup).\
options(selectinload(models.UserGroup.roles)).\
where(models.UserGroup.user_group_id == user_group_id)
result = await self._db_session.execute(query)
user_group_db = result.scalars().first()
if not user_group_db:
return None
user_group_db.roles.remove(role)
await self._db_session.commit()
await self._db_session.refresh(user_group_db)
return user_group_db
async def get_user_group_roles(self, user_group_id: UUID) -> List[models.Role]:
"""
Get all roles from a user group.
"""
query = select(models.Role). \
options(selectinload(models.Role.permissions)). \
join(models.UserGroup.roles). \
filter(models.UserGroup.user_group_id == user_group_id)
result = await self._db_session.execute(query)
return result.scalars().all()