Skip to content

Commit

Permalink
Removal of group or user (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
KWMORALE committed Apr 19, 2021
1 parent 943eeab commit 8df8aa3
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 16 deletions.
4 changes: 3 additions & 1 deletion mwdb/model/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class APIKey(db.Model):
__tablename__ = "api_key"

id = db.Column(UUID(as_uuid=True), primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user_id = db.Column(
db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
issued_on = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
issued_by = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

Expand Down
8 changes: 6 additions & 2 deletions mwdb/model/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ class Comment(db.Model):
comment = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)

object_id = db.Column(db.Integer, db.ForeignKey("object.id"), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
object_id = db.Column(
db.Integer, db.ForeignKey("object.id", ondelete="CASCADE"), nullable=False
)
user_id = db.Column(
db.Integer, db.ForeignKey("user.id", ondelete="SET NULL"), nullable=True
)

author = db.relationship("User", lazy="joined")

Expand Down
14 changes: 14 additions & 0 deletions mwdb/model/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ class Group(db.Model):
)
users = association_proxy("members", "user", creator=lambda user: Member(user=user))

permissions = db.relationship(
"ObjectPermission",
back_populates="group",
cascade="all, delete",
passive_deletes=True,
)

attributes = db.relationship(
"MetakeyPermission",
back_populates="group",
cascade="all, delete",
passive_deletes=True,
)

PUBLIC_GROUP_NAME = "public"
# These groups are just pre-created for convenience by 'mwdb-core configure'
DEFAULT_EVERYTHING_GROUP_NAME = "everything"
Expand Down
9 changes: 7 additions & 2 deletions mwdb/model/metakey.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class MetakeyPermission(db.Model):
)
group_id = db.Column(
db.Integer,
db.ForeignKey("group.id"),
db.ForeignKey("group.id", ondelete="CASCADE"),
primary_key=True,
autoincrement=False,
index=True,
Expand All @@ -101,7 +101,12 @@ class MetakeyPermission(db.Model):
lazy="joined",
back_populates="permissions",
)
group = db.relationship("Group", foreign_keys=[group_id], lazy="joined")
group = db.relationship(
"Group",
foreign_keys=[group_id],
lazy="joined",
back_populates="attributes",
)

@property
def group_name(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Cascades delete user and group
Revision ID: bd88eb7eec2b
Revises: f4ccb4be2170
Create Date: 2021-04-19 12:41:18.182462
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "bd88eb7eec2b"
down_revision = "f4ccb4be2170"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint("api_key_user_id_fkey", "api_key", type_="foreignkey")
op.create_foreign_key(
None, "api_key", "user", ["user_id"], ["id"], ondelete="CASCADE"
)
op.alter_column("comment", "user_id", existing_type=sa.INTEGER(), nullable=True)
op.drop_constraint("comment_user_id_fkey", "comment", type_="foreignkey")
op.drop_constraint("comment_object_id_fkey", "comment", type_="foreignkey")
op.create_foreign_key(
None, "comment", "user", ["user_id"], ["id"], ondelete="SET NULL"
)
op.create_foreign_key(
None, "comment", "object", ["object_id"], ["id"], ondelete="CASCADE"
)
op.drop_constraint(
"metakey_permission_group_id_fkey", "metakey_permission", type_="foreignkey"
)
op.create_foreign_key(
None, "metakey_permission", "group", ["group_id"], ["id"], ondelete="CASCADE"
)
op.drop_constraint(
"permission_related_user_id_fkey", "permission", type_="foreignkey"
)
op.drop_constraint("permission_group_id_fkey", "permission", type_="foreignkey")
op.drop_constraint("permission_object_id_fkey", "permission", type_="foreignkey")
op.create_foreign_key(
None, "permission", "user", ["related_user_id"], ["id"], ondelete="SET NULL"
)
op.create_foreign_key(
None, "permission", "object", ["object_id"], ["id"], ondelete="CASCADE"
)
op.create_foreign_key(
None, "permission", "group", ["group_id"], ["id"], ondelete="CASCADE"
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "permission", type_="foreignkey")
op.drop_constraint(None, "permission", type_="foreignkey")
op.drop_constraint(None, "permission", type_="foreignkey")
op.create_foreign_key(
"permission_object_id_fkey", "permission", "object", ["object_id"], ["id"]
)
op.create_foreign_key(
"permission_group_id_fkey", "permission", "group", ["group_id"], ["id"]
)
op.create_foreign_key(
"permission_related_user_id_fkey",
"permission",
"user",
["related_user_id"],
["id"],
)
op.drop_constraint(None, "metakey_permission", type_="foreignkey")
op.create_foreign_key(
"metakey_permission_group_id_fkey",
"metakey_permission",
"group",
["group_id"],
["id"],
)
op.drop_constraint(None, "comment", type_="foreignkey")
op.drop_constraint(None, "comment", type_="foreignkey")
op.create_foreign_key(
"comment_object_id_fkey", "comment", "object", ["object_id"], ["id"]
)
op.create_foreign_key(
"comment_user_id_fkey", "comment", "user", ["user_id"], ["id"]
)
op.alter_column("comment", "user_id", existing_type=sa.INTEGER(), nullable=False)
op.drop_constraint(None, "api_key", type_="foreignkey")
op.create_foreign_key(
"api_key_user_id_fkey", "api_key", "user", ["user_id"], ["id"]
)
# ### end Alembic commands ###
10 changes: 7 additions & 3 deletions mwdb/model/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ class ObjectPermission(db.Model):

object_id = db.Column(
db.Integer,
db.ForeignKey("object.id"),
db.ForeignKey("object.id", ondelete="CASCADE"),
primary_key=True,
autoincrement=False,
index=True,
)
group_id = db.Column(
db.Integer,
db.ForeignKey("group.id"),
db.ForeignKey("group.id", ondelete="CASCADE"),
primary_key=True,
autoincrement=False,
index=True,
Expand All @@ -75,7 +75,9 @@ class ObjectPermission(db.Model):

reason_type = db.Column(db.String(32))
related_object_id = db.Column(db.Integer, db.ForeignKey("object.id"))
related_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
related_user_id = db.Column(
db.Integer, db.ForeignKey("user.id", ondelete="SET NULL"), index=True
)

object = db.relationship(
"Object",
Expand All @@ -93,12 +95,14 @@ class ObjectPermission(db.Model):
"User",
foreign_keys=[related_user_id],
lazy="joined",
back_populates="permissions",
)

group = db.relationship(
"Group",
foreign_keys=[group_id],
lazy="joined",
back_populates="permissions",
)

@property
Expand Down
13 changes: 11 additions & 2 deletions mwdb/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,21 @@ class User(db.Model):
groups = association_proxy(
"memberships", "group", creator=lambda group: Member(group=group)
)
permissions = db.relationship(
"ObjectPermission",
back_populates="related_user",
)
favorites = db.relationship(
"Object", secondary=favorites, back_populates="followers", lazy="joined"
)

comments = db.relationship("Comment", back_populates="author")
api_keys = db.relationship("APIKey", foreign_keys="APIKey.user_id", backref="user")
comments = db.relationship(
"Comment",
back_populates="author",
)
api_keys = db.relationship(
"APIKey", foreign_keys="APIKey.user_id", backref="user", cascade="all, delete"
)
registrar = db.relationship(
"User", foreign_keys="User.registered_by", remote_side=[id], uselist=False
)
Expand Down
46 changes: 46 additions & 0 deletions mwdb/resources/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,52 @@ def put(self, name):
schema = GroupSuccessResponseSchema()
return schema.dump({"name": group.name})

@requires_authorization
@requires_capabilities(Capabilities.manage_users)
def delete(self, name):
"""
---
summary: Delete group
description: |
Remove group from database.
Requires `manage_users` capability.
security:
- bearerAuth: []
tags:
- group
parameters:
- in: path
name: name
schema:
type: string
description: Group name
responses:
200:
description: When group was removed successfully
content:
application/json:
schema: GroupSuccessResponseSchema
403:
description: When user doesn't have `manage_users` capability.
404:
description: When group doesn't exist
"""
group = (db.session.query(Group).filter(Group.name == name)).first()

if group is None:
raise NotFound("No such group")

if group.immutable is True:
raise Forbidden(f"Group '{name}' is immutable and can't be removed.")

db.session.delete(group)
db.session.commit()

logger.info("Group was deleted", extra={"group": name})
schema = GroupSuccessResponseSchema()
return schema.dump({"name": name})


class GroupMemberResource(Resource):
@requires_authorization
Expand Down
53 changes: 53 additions & 0 deletions mwdb/resources/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,12 @@ def get(self, login):
schema: UserItemResponseSchema
403:
description: When user doesn't have `manage_users` capability.
404:
description: When user doesn't exist.
"""
obj = db.session.query(User).filter(User.login == login).first()
if obj is None:
raise NotFound("No such user")
schema = UserItemResponseSchema()
return schema.dump(obj)

Expand Down Expand Up @@ -477,6 +481,55 @@ def put(self, login):
schema = UserSuccessResponseSchema()
return schema.dump({"login": user_login_obj["login"]})

@requires_authorization
@requires_capabilities(Capabilities.manage_users)
def delete(self, login):
"""
---
summary: Delete user
description: |
Remove user from database.
Requires `manage_users` capability.
security:
- bearerAuth: []
tags:
- user
parameters:
- in: path
name: login
schema:
type: string
description: User login
responses:
200:
description: When user was removed successfully
content:
application/json:
schema: UserSuccessResponseSchema
403:
description: When user doesn't have `manage_users` capability.
404:
description: When user doesn't exist.
"""
if g.auth_user.login == login:
raise Forbidden("You can't remove yourself from the database.")
user = db.session.query(User).filter(User.login == login).first()

if user is None:
raise NotFound("No such user")

group = (db.session.query(Group).filter(Group.name == login)).first()

db.session.delete(user)
db.session.delete(group)
db.session.commit()

logger.info("User was deleted", extra={"user": login})

schema = UserSuccessResponseSchema()
return schema.dump({"login": login})


class UserProfileResource(Resource):
@requires_authorization
Expand Down
3 changes: 2 additions & 1 deletion tests/backend/relations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .utils import MwdbTest, random_name
import requests

from .utils import MwdbTest, random_name


class RelationTestEntity(object):
def __init__(self, context, name):
Expand Down

0 comments on commit 8df8aa3

Please sign in to comment.