Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
db36ccf
Add user permission tables and rpc service
wvangeit May 26, 2025
00f61ee
Shorten func foreign key
wvangeit May 26, 2025
63f76a3
Shorten foreign key
wvangeit May 26, 2025
71e62b7
All web and api tests working with func permissions
wvangeit May 27, 2025
731687e
Add more function permission tests
wvangeit May 27, 2025
35235ed
Fix last commit
wvangeit May 27, 2025
784827c
Merge branch 'master' into func_user_permissions
wvangeit May 27, 2025
97c2272
Add db migration script for function access rights
wvangeit May 28, 2025
9970b4d
Update openapi specs
wvangeit May 28, 2025
2895da8
Work around mypy bug
wvangeit May 28, 2025
1906e8f
Merge branch 'master' into func_user_permissions
wvangeit May 28, 2025
0ab2a55
Address comments in the PR
wvangeit May 28, 2025
cb53ca1
Merge branch 'func_user_permissions' of github.com:wvangeit/osparc-si…
wvangeit May 28, 2025
9f1e354
Remove the user_id column from function access tables
wvangeit May 28, 2025
222b59d
Removed user_id column from function access tables
wvangeit May 28, 2025
7f63f95
Merge branch 'master' into func_user_permissions
wvangeit May 28, 2025
6971be0
Replace more positiveint with userid
wvangeit May 28, 2025
6caac7c
Merge branch 'func_user_permissions' of github.com:wvangeit/osparc-si…
wvangeit May 28, 2025
bed6155
Last switch to UserID
wvangeit May 28, 2025
330943d
Merge branch 'master' into func_user_permissions
wvangeit May 30, 2025
3395246
Remove func migration script
wvangeit May 30, 2025
3dfd738
Fix function db migration after master merge
wvangeit May 30, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
FunctionClassSpecificData,
FunctionID,
FunctionIDNotFoundError,
FunctionIDString,
FunctionInputs,
FunctionInputSchema,
FunctionInputsList,
Expand All @@ -25,6 +26,7 @@
FunctionJobStatus,
FunctionOutputs,
FunctionOutputSchema,
FunctionReadAccessDeniedError,
FunctionSchemaClass,
JSONFunctionInputSchema,
JSONFunctionOutputSchema,
Expand Down Expand Up @@ -54,6 +56,7 @@
"FunctionID",
"FunctionIDNotFoundError",
"FunctionIDNotFoundError",
"FunctionIDString",
"FunctionInputSchema",
"FunctionInputs",
"FunctionInputs",
Expand All @@ -80,6 +83,7 @@
"FunctionJobStatus",
"FunctionOutputSchema",
"FunctionOutputs",
"FunctionReadAccessDeniedError",
"FunctionSchemaClass",
"FunctionToRegister",
"FunctionToRegister",
Expand Down
100 changes: 99 additions & 1 deletion packages/models-library/src/models_library/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
from models_library import projects
from models_library.basic_regex import UUID_RE_BASE
from models_library.basic_types import ConstrainedStr
from models_library.groups import GroupID
from models_library.services_types import ServiceKey, ServiceVersion
from pydantic import BaseModel, Field
from models_library.users import UserID
from pydantic import BaseModel, ConfigDict, Field

from .projects import ProjectID
from .utils.change_case import snake_to_camel

FunctionID: TypeAlias = UUID
FunctionJobID: TypeAlias = UUID
Expand Down Expand Up @@ -241,6 +244,56 @@ class FunctionInputsValidationError(FunctionBaseError):
msg_template: str = "Function inputs validation failed: {error}"


class FunctionReadAccessDeniedError(FunctionBaseError):
msg_template: str = "Function {function_id} read access denied for user {user_id}"


class FunctionJobReadAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} read access denied for user {user_id}"
)


class FunctionJobCollectionReadAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} read access denied for user {user_id}"
)


class FunctionWriteAccessDeniedError(FunctionBaseError):
msg_template: str = "Function {function_id} write access denied for user {user_id}"


class FunctionJobWriteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} write access denied for user {user_id}"
)


class FunctionJobCollectionWriteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} write access denied for user {user_id}"
)


class FunctionExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function {function_id} execute access denied for user {user_id}"
)


class FunctionJobExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} execute access denied for user {user_id}"
)


class FunctionJobCollectionExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} execute access denied for user {user_id}"
)


class FunctionJobDB(BaseModel):
function_uuid: FunctionID
title: str = ""
Expand Down Expand Up @@ -286,3 +339,48 @@ class FunctionJobCollectionsListFilters(BaseModel):
"""Filters for listing function job collections"""

has_function_id: FunctionIDString | None = None


class FunctionAccessRights(BaseModel):
read: bool = False
write: bool = False
execute: bool = False

model_config = ConfigDict(
alias_generator=snake_to_camel,
populate_by_name=True,
extra="forbid",
)


class FunctionUserAccessRights(FunctionAccessRights):
user_id: UserID


class FunctionGroupAccessRights(FunctionAccessRights):
group_id: GroupID


class FunctionAccessRightsDB(BaseModel):
user_id: UserID | None = None
group_id: GroupID | None = None
read: bool = False
write: bool = False
execute: bool = False

model_config = ConfigDict(
alias_generator=snake_to_camel,
populate_by_name=True,
extra="forbid",
)


FunctionJobAccessRights: TypeAlias = FunctionAccessRights
FunctionJobAccessRightsDB: TypeAlias = FunctionAccessRightsDB
FunctionJobUserAccessRights: TypeAlias = FunctionUserAccessRights
FunctionJobGroupAccessRights: TypeAlias = FunctionGroupAccessRights

FunctionJobCollectionAccessRights: TypeAlias = FunctionAccessRights
FunctionJobCollectionAccessRightsDB: TypeAlias = FunctionAccessRightsDB
FunctionJobCollectionUserAccessRights: TypeAlias = FunctionUserAccessRights
FunctionJobCollectionGroupAccessRights: TypeAlias = FunctionGroupAccessRights
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""Add function access rights
Revision ID: d3982ce629b9
Revises: 278daef7e99d
Create Date: 2025-05-28 06:29:49.436254+00:00
"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "d3982ce629b9"
down_revision = "278daef7e99d"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"funcapi_function_job_collections_access_rights",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column(
"function_job_collection_uuid",
postgresql.UUID(as_uuid=True),
nullable=False,
),
sa.Column("user_id", sa.BigInteger(), nullable=True),
sa.Column("group_id", sa.BigInteger(), nullable=True),
sa.Column("read", sa.Boolean(), nullable=True),
sa.Column("write", sa.Boolean(), nullable=True),
sa.Column("execute", sa.Boolean(), nullable=True),
sa.Column(
"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.CheckConstraint(
"user_id IS NULL AND group_id IS NOT NULL OR user_id IS NOT NULL AND group_id IS NULL",
name="ck_user_or_group_exclusive",
),
sa.ForeignKeyConstraint(
["function_job_collection_uuid"],
["funcapi_function_job_collections.uuid"],
name="fk_func_access_to_func_job_colls_to_func_job_coll_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["group_id"],
["groups.gid"],
name="fk_func_access_to_groups_group_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
name="fk_func_access_to_users_user_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user_id",
"group_id",
name="uq_funcapi_function_job_colls_access_rights_user_group",
),
)
op.create_table(
"funcapi_function_jobs_access_rights",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("function_job_uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=True),
sa.Column("group_id", sa.BigInteger(), nullable=True),
sa.Column("read", sa.Boolean(), nullable=True),
sa.Column("write", sa.Boolean(), nullable=True),
sa.Column("execute", sa.Boolean(), nullable=True),
sa.Column(
"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.CheckConstraint(
"user_id IS NULL AND group_id IS NOT NULL OR user_id IS NOT NULL AND group_id IS NULL",
name="ck_user_or_group_exclusive",
),
sa.ForeignKeyConstraint(
["function_job_uuid"],
["funcapi_function_jobs.uuid"],
name="fk_func_access_to_func_jobs_to_func_job_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["group_id"],
["groups.gid"],
name="fk_func_access_to_groups_group_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
name="fk_func_access_to_users_user_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user_id",
"group_id",
name="uq_funcapi_function_jobs_access_rights_user_group",
),
)
op.create_table(
"funcapi_functions_access_rights",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("function_uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=True),
sa.Column("group_id", sa.BigInteger(), nullable=True),
sa.Column("read", sa.Boolean(), nullable=True),
sa.Column("write", sa.Boolean(), nullable=True),
sa.Column("execute", sa.Boolean(), nullable=True),
sa.Column(
"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.CheckConstraint(
"user_id IS NULL AND group_id IS NOT NULL OR user_id IS NOT NULL AND group_id IS NULL",
name="ck_user_or_group_exclusive",
),
sa.ForeignKeyConstraint(
["function_uuid"],
["funcapi_functions.uuid"],
name="fk_func_access_to_func_to_func_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["group_id"],
["groups.gid"],
name="fk_func_access_to_groups_group_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
name="fk_func_access_to_users_user_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user_id", "group_id", name="uq_funcapi_functions_access_rights_user_group"
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("funcapi_functions_access_rights")
op.drop_table("funcapi_function_jobs_access_rights")
op.drop_table("funcapi_function_job_collections_access_rights")
# ### end Alembic commands ###
Loading
Loading