Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ refactor project access rights 🗃️🚨 #6060

Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
efd2186
refactor project access rights
matusdrobuliak66 Jul 12, 2024
d7736dc
refactor get_project_access_rights_for_user function
matusdrobuliak66 Jul 12, 2024
c122202
openapi specs
matusdrobuliak66 Jul 12, 2024
6c85336
minor fix
matusdrobuliak66 Jul 12, 2024
fca9ae6
minor fix
matusdrobuliak66 Jul 12, 2024
5822534
fix some failing tests
matusdrobuliak66 Jul 15, 2024
83d0035
fix
matusdrobuliak66 Jul 15, 2024
8e4f736
fix tests
matusdrobuliak66 Jul 15, 2024
c480eea
fix NewProject fixture - user id is mandatory
matusdrobuliak66 Jul 15, 2024
c8601ee
fix additional tests
matusdrobuliak66 Jul 15, 2024
38c7189
fix additional tests
matusdrobuliak66 Jul 15, 2024
71d32df
fix additional tests
matusdrobuliak66 Jul 15, 2024
8ace761
fix additional tests
matusdrobuliak66 Jul 15, 2024
bb15536
trying to fix integration test
matusdrobuliak66 Jul 16, 2024
4d0e1a0
fix additional bunch of tests
matusdrobuliak66 Jul 16, 2024
ab31bd3
fix additional integration tests
matusdrobuliak66 Jul 16, 2024
71c1b38
fix additional tests
matusdrobuliak66 Jul 16, 2024
eb2bedb
fix integration 02 tests
matusdrobuliak66 Jul 16, 2024
2be2d65
Merge branch 'master' of github.com:ITISFoundation/osparc-simcore int…
matusdrobuliak66 Jul 16, 2024
335f060
fix unit test
matusdrobuliak66 Jul 16, 2024
901a3d1
fix unit test
matusdrobuliak66 Jul 16, 2024
737a268
trying to fix system e2e test
matusdrobuliak66 Jul 16, 2024
3ee9330
Make project owner AND gid in project_to_group non-nullable
matusdrobuliak66 Jul 16, 2024
3e0d6b3
final cleanup
matusdrobuliak66 Jul 16, 2024
030a2dc
Merge branch 'master' into is716/move-access-rights-from-projects-table
matusdrobuliak66 Jul 16, 2024
7143d93
remove mandatory prj_owner
matusdrobuliak66 Jul 16, 2024
85e5fce
remove mandatory prj_owner
matusdrobuliak66 Jul 16, 2024
c46848a
Merge branch 'master' into is716/move-access-rights-from-projects-table
matusdrobuliak66 Jul 16, 2024
321fe7a
final cleanup
matusdrobuliak66 Jul 16, 2024
b77341a
Merge branch 'is716/move-access-rights-from-projects-table' of github…
matusdrobuliak66 Jul 16, 2024
83dd657
remove redundant comment
matusdrobuliak66 Jul 17, 2024
7d9bf2e
Merge branch 'master' into is716/move-access-rights-from-projects-table
matusdrobuliak66 Jul 17, 2024
54eb16a
review @bisgaard-itis
matusdrobuliak66 Jul 17, 2024
304fe7c
review @GitHK
matusdrobuliak66 Jul 17, 2024
8b452e7
review @prespo
matusdrobuliak66 Jul 17, 2024
8c12b64
review @prespo
matusdrobuliak66 Jul 17, 2024
9ca03fa
review @GitHK
matusdrobuliak66 Jul 17, 2024
b515abb
Merge branch 'master' into is716/move-access-rights-from-projects-table
matusdrobuliak66 Jul 17, 2024
7d624f9
Merge branch 'master' into is716/move-access-rights-from-projects-table
matusdrobuliak66 Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions api/specs/web-server/_projects_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
""" Helper script to automatically generate OAS

This OAS are the source of truth
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved
"""

# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=unused-variable
# pylint: disable=too-many-arguments


from _common import assert_handler_signature_against_model
from fastapi import APIRouter, status
from models_library.generics import Envelope
from models_library.projects import ProjectID
from models_library.users import GroupID
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.projects._common_models import ProjectPathParams
from simcore_service_webserver.projects._groups_api import ProjectGroupGet
from simcore_service_webserver.projects._groups_handlers import (
_ProjectsGroupsBodyParams,
_ProjectsGroupsPathParams,
)

router = APIRouter(
prefix=f"/{API_VTAG}",
tags=[
"projects",
],
)


### Projects groups


@router.post(
"/projects/{project_id}/groups/{group_id}",
response_model=Envelope[ProjectGroupGet],
status_code=status.HTTP_201_CREATED,
)
async def create_project_group(
project_id: ProjectID, group_id: GroupID, body: _ProjectsGroupsBodyParams
):
...


assert_handler_signature_against_model(create_project_group, _ProjectsGroupsPathParams)
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved


@router.get(
"/projects/{project_id}/groups",
response_model=Envelope[list[ProjectGroupGet]],
)
async def list_project_groups(project_id: ProjectID):
...


assert_handler_signature_against_model(list_project_groups, ProjectPathParams)


@router.put(
"/projects/{project_id}/groups/{group_id}",
response_model=Envelope[ProjectGroupGet],
)
async def update_project_group(
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved
project_id: ProjectID, group_id: GroupID, body: _ProjectsGroupsBodyParams
):
...


assert_handler_signature_against_model(update_project_group, _ProjectsGroupsPathParams)


@router.delete(
"/projects/{project_id}/groups/{group_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_project_group(project_id: ProjectID, group_id: GroupID):
...


assert_handler_signature_against_model(delete_project_group, _ProjectsGroupsPathParams)
1 change: 1 addition & 0 deletions api/specs/web-server/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"_projects_nodes_pricing_unit",
"_projects_comments",
"_projects_crud",
"_projects_groups",
"_projects_metadata",
"_projects_nodes",
"_projects_ports",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""create project_to_groups table

Revision ID: 19f3d9085636
Revises: d1fafda96f4c
Create Date: 2024-07-12 07:23:52.049378+00:00

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "19f3d9085636"
down_revision = "d1fafda96f4c"
branch_labels = None
depends_on = None


# ------------------------ TRIGGERS
new_project_trigger = sa.DDL(
"""
DROP TRIGGER IF EXISTS project_creation on projects;
CREATE TRIGGER project_creation
AFTER INSERT ON projects
FOR EACH ROW
EXECUTE PROCEDURE set_project_to_owner_group();
"""
)


# --------------------------- PROCEDURES
assign_project_access_rights_to_owner_group_procedure = sa.DDL(
"""
CREATE OR REPLACE FUNCTION set_project_to_owner_group() RETURNS TRIGGER AS $$
DECLARE
group_id BIGINT;
BEGIN
-- Fetch the group_id based on the owner from the other table
SELECT u.primary_gid INTO group_id
FROM users u
WHERE u.id = NEW.prj_owner
LIMIT 1;

IF group_id IS NOT NULL THEN
IF TG_OP = 'INSERT' THEN
INSERT INTO "project_to_groups" ("gid", "project_uuid", "read", "write", "delete") VALUES (group_id, NEW.uuid, TRUE, TRUE, TRUE);
END IF;
END IF;

RETURN NULL;
END; $$ LANGUAGE 'plpgsql';
"""
)


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"project_to_groups",
sa.Column("project_uuid", sa.String(), nullable=False),
sa.Column("gid", sa.BigInteger(), nullable=False),
sa.Column(
"read", sa.Boolean(), server_default=sa.text("false"), nullable=False
),
sa.Column(
"write", sa.Boolean(), server_default=sa.text("false"), nullable=False
),
sa.Column(
"delete", sa.Boolean(), server_default=sa.text("false"), nullable=False
),
sa.Column(
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved
"created",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"modified",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["gid"],
["groups.gid"],
name="fk_project_to_groups_gid_groups",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["project_uuid"],
["projects.uuid"],
name="fk_project_to_groups_project_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.UniqueConstraint("project_uuid", "gid"),
)
op.create_index(
op.f("ix_project_to_groups_project_uuid"),
"project_to_groups",
["project_uuid"],
unique=False,
)
# ### end Alembic commands ###

# Migration of access rights from projects table to new project_to_groups table
op.execute(
sa.DDL(
"""
INSERT INTO project_to_groups
select
projects.uuid as project_uuid,
CAST(js.key as bigint) as gid,
CAST(js.value ->> 'read' as bool) as read,
CAST(js.value ->> 'write' as bool) as write,
CAST(js.value ->> 'delete' as bool) as delete,
CURRENT_TIMESTAMP as created,
CURRENT_TIMESTAMP as modified
from projects,
json_each(projects.access_rights::json) AS js;
"""
)
)

op.execute(assign_project_access_rights_to_owner_group_procedure)
op.execute(new_project_trigger)


def downgrade():
op.execute(new_project_trigger)
op.execute(assign_project_access_rights_to_owner_group_procedure)

# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(
op.f("ix_project_to_groups_project_uuid"), table_name="project_to_groups"
)
op.drop_table("project_to_groups")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import sqlalchemy as sa
from sqlalchemy.sql import expression

from ._common import column_created_datetime, column_modified_datetime
from .base import metadata
from .groups import groups
from .projects import projects

project_to_groups = sa.Table(
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved
"project_to_groups",
metadata,
sa.Column(
"project_uuid",
sa.String,
sa.ForeignKey(
projects.c.uuid,
name="fk_project_to_groups_project_uuid",
ondelete="CASCADE",
onupdate="CASCADE",
),
index=True,
nullable=False,
doc="project reference for this table",
),
sa.Column(
"gid",
sa.BigInteger,
sa.ForeignKey(
groups.c.gid,
name="fk_project_to_groups_gid_groups",
onupdate="CASCADE",
ondelete="CASCADE",
),
nullable=False,
doc="Group unique IDentifier",
),
# Access Rights flags ---
sa.Column(
"read",
sa.Boolean,
nullable=False,
server_default=expression.false(),
doc="If true, group can open the project",
),
sa.Column(
"write",
sa.Boolean,
nullable=False,
server_default=expression.false(),
doc="If true, group can modify the project",
),
sa.Column(
"delete",
sa.Boolean,
nullable=False,
server_default=expression.false(),
doc="If true, group can delete the project",
),
# -----
column_created_datetime(timezone=True),
column_modified_datetime(timezone=True),
sa.UniqueConstraint("project_uuid", "gid"),
)
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,48 @@ class ProjectType(enum.Enum):
doc="If true, the project is by default not listed in the API",
),
)


# ------------------------ TRIGGERS
new_project_trigger = sa.DDL(
"""
DROP TRIGGER IF EXISTS project_creation on projects;
CREATE TRIGGER project_creation
AFTER INSERT ON projects
FOR EACH ROW
EXECUTE PROCEDURE set_project_to_owner_group();
"""
)


# --------------------------- PROCEDURES
assign_project_access_rights_to_owner_group_procedure = sa.DDL(
"""
CREATE OR REPLACE FUNCTION set_project_to_owner_group() RETURNS TRIGGER AS $$
DECLARE
group_id BIGINT;
BEGIN
-- Fetch the group_id based on the owner from the other table
SELECT u.primary_gid INTO group_id
FROM users u
WHERE u.id = NEW.prj_owner
LIMIT 1;

IF group_id IS NOT NULL THEN
IF TG_OP = 'INSERT' THEN
INSERT INTO "project_to_groups" ("gid", "project_uuid", "read", "write", "delete") VALUES (group_id, NEW.uuid, TRUE, TRUE, TRUE);
END IF;
END IF;
RETURN NULL;
END; $$ LANGUAGE 'plpgsql';
"""
)
matusdrobuliak66 marked this conversation as resolved.
Show resolved Hide resolved

sa.event.listen(
projects, "after_create", assign_project_access_rights_to_owner_group_procedure
)
sa.event.listen(
projects,
"after_create",
new_project_trigger,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from models_library.services_resources import ServiceResourcesDictHelpers
from simcore_postgres_database.utils_projects_nodes import ProjectNodeCreate
from simcore_service_webserver.projects._db_utils import DB_EXCLUSIVE_COLUMNS
from simcore_service_webserver.projects._groups_db import update_or_insert_project_group
from simcore_service_webserver.projects.db import APP_PROJECT_DBAPI, ProjectDBAPI
from simcore_service_webserver.projects.models import ProjectDict
from simcore_service_webserver.utils import now_str
Expand Down Expand Up @@ -84,6 +85,23 @@ async def create_project(
for node_id in project_data.get("workbench", {})
},
)

if params_override and (
params_override.get("access_rights") or params_override.get("accessRights")
):
_access_rights = params_override.get("access_rights", {}) | params_override.get(
"accessRights", {}
)
for group_id, permissions in _access_rights.items():
await update_or_insert_project_group(
app,
new_project["uuid"],
group_id=int(group_id),
read=permissions["read"],
write=permissions["write"],
delete=permissions["delete"],
)

try:
uuidlib.UUID(str(project_data["uuid"]))
assert new_project["uuid"] == project_data["uuid"]
Expand Down Expand Up @@ -112,8 +130,8 @@ def __init__(
self,
params_override: dict | None = None,
app: web.Application | None = None,
user_id: int | None = None,
*,
user_id: int,
product_name: str,
tests_data_dir: Path,
force_uuid: bool = False,
Expand Down
Loading
Loading