ckanext-permissions 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ckanext/__init__.py +9 -0
- ckanext/permissions/__init__.py +0 -0
- ckanext/permissions/cli.py +80 -0
- ckanext/permissions/const.py +10 -0
- ckanext/permissions/helpers.py +51 -0
- ckanext/permissions/implementation/__init__.py +5 -0
- ckanext/permissions/implementation/permission_labels.py +45 -0
- ckanext/permissions/logic/__init__.py +0 -0
- ckanext/permissions/logic/action.py +113 -0
- ckanext/permissions/logic/auth.py +82 -0
- ckanext/permissions/logic/schema.py +54 -0
- ckanext/permissions/logic/validators.py +112 -0
- ckanext/permissions/migration/permissions/alembic.ini +74 -0
- ckanext/permissions/migration/permissions/env.py +85 -0
- ckanext/permissions/migration/permissions/script.py.mako +24 -0
- ckanext/permissions/migration/permissions/versions/a849104ccfdc_init_tables.py +69 -0
- ckanext/permissions/model.py +148 -0
- ckanext/permissions/plugin.py +67 -0
- ckanext/permissions/tests/__init__.py +0 -0
- ckanext/permissions/tests/actions/test_permission.py +68 -0
- ckanext/permissions/tests/actions/test_role.py +110 -0
- ckanext/permissions/tests/conftest.py +52 -0
- ckanext/permissions/tests/test_autoassign.py +26 -0
- ckanext/permissions/tests/test_helpers.py +67 -0
- ckanext/permissions/tests/test_permission_labels.py +28 -0
- ckanext/permissions/tests/test_utils.py +275 -0
- ckanext/permissions/tests/test_validators.py +98 -0
- ckanext/permissions/types.py +34 -0
- ckanext/permissions/utils.py +197 -0
- ckanext/permissions_manager/__init__.py +0 -0
- ckanext/permissions_manager/helpers.py +23 -0
- ckanext/permissions_manager/plugin.py +48 -0
- ckanext/permissions_manager/views.py +351 -0
- ckanext_permissions-0.2.0.dist-info/METADATA +104 -0
- ckanext_permissions-0.2.0.dist-info/RECORD +39 -0
- ckanext_permissions-0.2.0.dist-info/WHEEL +5 -0
- ckanext_permissions-0.2.0.dist-info/entry_points.txt +6 -0
- ckanext_permissions-0.2.0.dist-info/licenses/LICENSE +661 -0
- ckanext_permissions-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from __future__ import with_statement
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from logging.config import fileConfig
|
|
7
|
+
|
|
8
|
+
from alembic import context
|
|
9
|
+
from sqlalchemy import engine_from_config, pool
|
|
10
|
+
|
|
11
|
+
# this is the Alembic Config object, which provides
|
|
12
|
+
# access to the values within the .ini file in use.
|
|
13
|
+
config = context.config
|
|
14
|
+
|
|
15
|
+
# Interpret the config file for Python logging.
|
|
16
|
+
# This line sets up loggers basically.
|
|
17
|
+
fileConfig(config.config_file_name)
|
|
18
|
+
|
|
19
|
+
# add your model's MetaData object here
|
|
20
|
+
# for 'autogenerate' support
|
|
21
|
+
# from myapp import mymodel
|
|
22
|
+
# target_metadata = mymodel.Base.metadata
|
|
23
|
+
target_metadata = None
|
|
24
|
+
|
|
25
|
+
# other values from the config, defined by the needs of env.py,
|
|
26
|
+
# can be acquired:
|
|
27
|
+
# my_important_option = config.get_main_option("my_important_option")
|
|
28
|
+
# ... etc.
|
|
29
|
+
|
|
30
|
+
name = os.path.basename(os.path.dirname(__file__))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_migrations_offline():
|
|
34
|
+
"""Run migrations in 'offline' mode.
|
|
35
|
+
|
|
36
|
+
This configures the context with just a URL
|
|
37
|
+
and not an Engine, though an Engine is acceptable
|
|
38
|
+
here as well. By skipping the Engine creation
|
|
39
|
+
we don't even need a DBAPI to be available.
|
|
40
|
+
|
|
41
|
+
Calls to context.execute() here emit the given string to the
|
|
42
|
+
script output.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
47
|
+
context.configure(
|
|
48
|
+
url=url,
|
|
49
|
+
target_metadata=target_metadata,
|
|
50
|
+
literal_binds=True,
|
|
51
|
+
version_table="{}_alembic_version".format(name),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
with context.begin_transaction():
|
|
55
|
+
context.run_migrations()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_migrations_online():
|
|
59
|
+
"""Run migrations in 'online' mode.
|
|
60
|
+
|
|
61
|
+
In this scenario we need to create an Engine
|
|
62
|
+
and associate a connection with the context.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
connectable = engine_from_config(
|
|
66
|
+
config.get_section(config.config_ini_section),
|
|
67
|
+
prefix="sqlalchemy.",
|
|
68
|
+
poolclass=pool.NullPool,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
with connectable.connect() as connection:
|
|
72
|
+
context.configure(
|
|
73
|
+
connection=connection,
|
|
74
|
+
target_metadata=target_metadata,
|
|
75
|
+
version_table="{}_alembic_version".format(name),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with context.begin_transaction():
|
|
79
|
+
context.run_migrations()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if context.is_offline_mode():
|
|
83
|
+
run_migrations_offline()
|
|
84
|
+
else:
|
|
85
|
+
run_migrations_online()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
${imports if imports else ""}
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = ${repr(up_revision)}
|
|
14
|
+
down_revision = ${repr(down_revision)}
|
|
15
|
+
branch_labels = ${repr(branch_labels)}
|
|
16
|
+
depends_on = ${repr(depends_on)}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
${upgrades if upgrades else "pass"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def downgrade():
|
|
24
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Init tables
|
|
2
|
+
|
|
3
|
+
Revision ID: a849104ccfdc
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2024-02-14 17:11:12.064281
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
from alembic import op
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = "a849104ccfdc"
|
|
14
|
+
down_revision = None
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
op.create_table(
|
|
21
|
+
"perm_role",
|
|
22
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
23
|
+
sa.Column("label", sa.String(), nullable=False),
|
|
24
|
+
sa.Column("description", sa.String(), nullable=False),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
op.create_table(
|
|
28
|
+
"perm_user_role",
|
|
29
|
+
sa.Column(
|
|
30
|
+
"user_id",
|
|
31
|
+
sa.String(),
|
|
32
|
+
sa.ForeignKey("user.id", ondelete="CASCADE"),
|
|
33
|
+
primary_key=True,
|
|
34
|
+
),
|
|
35
|
+
sa.Column(
|
|
36
|
+
"role_id",
|
|
37
|
+
sa.String(),
|
|
38
|
+
sa.ForeignKey("perm_role.id", ondelete="CASCADE"),
|
|
39
|
+
primary_key=True,
|
|
40
|
+
),
|
|
41
|
+
sa.Column(
|
|
42
|
+
"scope",
|
|
43
|
+
sa.String(),
|
|
44
|
+
primary_key=True,
|
|
45
|
+
default="global",
|
|
46
|
+
),
|
|
47
|
+
sa.Column(
|
|
48
|
+
"scope_id",
|
|
49
|
+
sa.String(),
|
|
50
|
+
nullable=True,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
op.create_table(
|
|
55
|
+
"perm_role_permission",
|
|
56
|
+
sa.Column(
|
|
57
|
+
"role_id",
|
|
58
|
+
sa.String(),
|
|
59
|
+
sa.ForeignKey("perm_role.id", ondelete="CASCADE"),
|
|
60
|
+
primary_key=True,
|
|
61
|
+
),
|
|
62
|
+
sa.Column("permission", sa.String(), nullable=False, primary_key=True),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def downgrade():
|
|
67
|
+
op.drop_table("perm_role_permission")
|
|
68
|
+
op.drop_table("perm_user_role")
|
|
69
|
+
op.drop_table("perm_role")
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Column, ForeignKey, String
|
|
6
|
+
from sqlalchemy.orm import Query, backref, relationship
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
import ckan.model as model
|
|
10
|
+
import ckan.types as types
|
|
11
|
+
from ckan.plugins import toolkit as tk
|
|
12
|
+
|
|
13
|
+
import ckanext.permissions.types as perm_types
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Role(tk.BaseModel):
|
|
19
|
+
__tablename__ = "perm_role"
|
|
20
|
+
|
|
21
|
+
id = Column(String, primary_key=True)
|
|
22
|
+
label = Column(String, nullable=False)
|
|
23
|
+
description = Column(String, nullable=False)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def create(cls, id: str, label: str, description: str) -> Self:
|
|
27
|
+
role = cls(id=id, label=label, description=description)
|
|
28
|
+
|
|
29
|
+
model.Session.add(role)
|
|
30
|
+
model.Session.commit()
|
|
31
|
+
|
|
32
|
+
return role
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def get(cls, role: str) -> Self | None:
|
|
36
|
+
return model.Session.query(cls).filter(cls.id == role).one_or_none()
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def all(cls) -> list[Self]:
|
|
40
|
+
return [role.dictize({}) for role in model.Session.query(cls).all()]
|
|
41
|
+
|
|
42
|
+
def update(self, description: str) -> None:
|
|
43
|
+
self.description = description
|
|
44
|
+
|
|
45
|
+
model.Session.commit()
|
|
46
|
+
|
|
47
|
+
def dictize(self, context: types.Context) -> perm_types.Role:
|
|
48
|
+
return perm_types.Role(
|
|
49
|
+
id=str(self.id),
|
|
50
|
+
label=str(self.label),
|
|
51
|
+
description=str(self.description),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def delete(self) -> None:
|
|
55
|
+
model.Session.delete(self)
|
|
56
|
+
model.Session.commit()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UserRole(tk.BaseModel):
|
|
60
|
+
__tablename__ = "perm_user_role"
|
|
61
|
+
|
|
62
|
+
user_id = Column(String, ForeignKey("user.id", ondelete="CASCADE"), primary_key=True)
|
|
63
|
+
role_id = Column(String, ForeignKey("perm_role.id", ondelete="CASCADE"), primary_key=True)
|
|
64
|
+
|
|
65
|
+
scope = Column(String, primary_key=True, default="global")
|
|
66
|
+
scope_id = Column(String, nullable=True)
|
|
67
|
+
|
|
68
|
+
user = relationship(
|
|
69
|
+
model.User,
|
|
70
|
+
backref=backref("roles", cascade="all, delete"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
role = relationship(Role, cascade="all, delete")
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def get(cls, user_id: str, scope: str = "global", scope_id: str | None = None) -> list[Self]:
|
|
77
|
+
query: Query = model.Session.query(cls).filter(cls.user_id == user_id).filter(cls.scope == scope)
|
|
78
|
+
|
|
79
|
+
if scope_id:
|
|
80
|
+
query = query.filter(cls.scope_id == scope_id)
|
|
81
|
+
|
|
82
|
+
return query.all()
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def create(
|
|
86
|
+
cls,
|
|
87
|
+
user_id: str,
|
|
88
|
+
role: str,
|
|
89
|
+
scope: str = "global",
|
|
90
|
+
scope_id: str | None = None,
|
|
91
|
+
) -> Self:
|
|
92
|
+
for user_role in cls.get(user_id, scope, scope_id):
|
|
93
|
+
if user_role.role_id != role:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
return user_role
|
|
97
|
+
|
|
98
|
+
user_role = cls(user_id=user_id, role_id=role, scope=scope, scope_id=scope_id)
|
|
99
|
+
|
|
100
|
+
model.Session.add(user_role)
|
|
101
|
+
model.Session.commit()
|
|
102
|
+
|
|
103
|
+
return user_role
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def clear_user_roles(cls, user_id: str, scope: str = "", scope_id: str | None = None) -> None:
|
|
107
|
+
perm = model.Session.query(UserRole).filter(UserRole.user_id == user_id)
|
|
108
|
+
|
|
109
|
+
if scope:
|
|
110
|
+
perm = perm.filter_by(scope=scope)
|
|
111
|
+
if scope_id:
|
|
112
|
+
perm = perm.filter_by(scope_id=scope_id)
|
|
113
|
+
|
|
114
|
+
perm.delete()
|
|
115
|
+
model.Session.commit()
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def delete(cls, user_id: str, role: str) -> None:
|
|
119
|
+
model.Session.query(cls).filter(cls.user_id == user_id, cls.role_id == role).delete()
|
|
120
|
+
model.Session.commit()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class RolePermission(tk.BaseModel):
|
|
124
|
+
__tablename__ = "perm_role_permission"
|
|
125
|
+
|
|
126
|
+
role_id = Column(String, ForeignKey("perm_role.id"), primary_key=True)
|
|
127
|
+
permission = Column(String, primary_key=True)
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def get(cls, role_id: str, permission: str) -> Self | None:
|
|
131
|
+
query: Query = model.Session.query(cls).filter(cls.role_id == role_id, cls.permission == permission)
|
|
132
|
+
|
|
133
|
+
return query.one_or_none()
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def create(cls, role_id: str, permission: str, defer_commit: bool = True) -> Self:
|
|
137
|
+
role_permission = cls(role_id=role_id, permission=permission)
|
|
138
|
+
|
|
139
|
+
model.Session.add(role_permission)
|
|
140
|
+
|
|
141
|
+
if defer_commit:
|
|
142
|
+
model.Session.commit()
|
|
143
|
+
|
|
144
|
+
return role_permission
|
|
145
|
+
|
|
146
|
+
def delete(self) -> None:
|
|
147
|
+
model.Session().autoflush = False
|
|
148
|
+
model.Session.delete(self)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ckan.plugins as p
|
|
4
|
+
import ckan.plugins.toolkit as tk
|
|
5
|
+
from ckan import types
|
|
6
|
+
|
|
7
|
+
from ckanext.permissions import const as perm_const
|
|
8
|
+
from ckanext.permissions import implementation
|
|
9
|
+
from ckanext.permissions import types as perm_types
|
|
10
|
+
from ckanext.permissions import utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@tk.blanket.cli
|
|
14
|
+
@tk.blanket.validators
|
|
15
|
+
@tk.blanket.actions
|
|
16
|
+
@tk.blanket.helpers
|
|
17
|
+
@tk.blanket.auth_functions
|
|
18
|
+
@tk.blanket.config_declarations
|
|
19
|
+
class PermissionsPlugin(implementation.PermissionLabels, p.SingletonPlugin):
|
|
20
|
+
p.implements(p.IConfigurer)
|
|
21
|
+
p.implements(p.ISignal)
|
|
22
|
+
|
|
23
|
+
_permissions_groups: perm_types.PermissionGroup | None = None
|
|
24
|
+
_permissions = dict[str, perm_types.PermissionDefinition]
|
|
25
|
+
|
|
26
|
+
# IConfigurer
|
|
27
|
+
|
|
28
|
+
def update_config(self, config_: tk.CKANConfig):
|
|
29
|
+
if not PermissionsPlugin._permissions_groups: # type: ignore
|
|
30
|
+
PermissionsPlugin._permissions_groups = list( # type: ignore
|
|
31
|
+
utils.parse_permission_group_schemas().values()
|
|
32
|
+
)
|
|
33
|
+
PermissionsPlugin._permissions = { # type: ignore
|
|
34
|
+
permission["key"]: permission
|
|
35
|
+
for group in PermissionsPlugin._permissions_groups # type: ignore
|
|
36
|
+
for permission in group["permissions"]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
tk.add_template_directory(config_, "templates")
|
|
40
|
+
|
|
41
|
+
# ISignal
|
|
42
|
+
|
|
43
|
+
def get_signal_subscriptions(self) -> types.SignalMapping:
|
|
44
|
+
return {tk.signals.action_succeeded: [self.assign_default_user_role]}
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def assign_default_user_role(
|
|
48
|
+
action_name: str,
|
|
49
|
+
context: types.Context,
|
|
50
|
+
data_dict: types.DataDict,
|
|
51
|
+
result: types.DataDict,
|
|
52
|
+
):
|
|
53
|
+
"""Assign the default user role to a new user
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
action_name: The name of the action
|
|
57
|
+
context: The action context
|
|
58
|
+
data_dict: The action payload
|
|
59
|
+
result: The action result
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if action_name != "user_create":
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
utils.assign_role_to_user(
|
|
66
|
+
result["id"], perm_const.Roles.Authenticated.value, "global"
|
|
67
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import ckan.plugins.toolkit as tk
|
|
4
|
+
from ckan.tests.helpers import call_action
|
|
5
|
+
|
|
6
|
+
from ckanext.permissions import model as perm_model
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.usefixtures("with_plugins", "clean_db")
|
|
10
|
+
class TestPermissionsUpdate:
|
|
11
|
+
def test_permissions_update(self):
|
|
12
|
+
result = call_action(
|
|
13
|
+
"permissions_update",
|
|
14
|
+
permissions={
|
|
15
|
+
"perm_1": {
|
|
16
|
+
"anonymous": False,
|
|
17
|
+
"authenticated": True,
|
|
18
|
+
"administrator": True,
|
|
19
|
+
},
|
|
20
|
+
"perm_2": {
|
|
21
|
+
"anonymous": False,
|
|
22
|
+
"authenticated": True,
|
|
23
|
+
"administrator": True,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
assert not result["missing_permissions"]
|
|
29
|
+
assert result["updated_permissions"] == {
|
|
30
|
+
"perm_1": {"authenticated": True, "administrator": True},
|
|
31
|
+
"perm_2": {"authenticated": True, "administrator": True},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
assert not perm_model.RolePermission.get("anonymous", "perm_1")
|
|
35
|
+
assert perm_model.RolePermission.get("authenticated", "perm_1")
|
|
36
|
+
assert perm_model.RolePermission.get("administrator", "perm_1")
|
|
37
|
+
|
|
38
|
+
result = call_action(
|
|
39
|
+
"permissions_update",
|
|
40
|
+
permissions={"perm_1": {"anonymous": True}},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert perm_model.RolePermission.get("anonymous", "perm_1")
|
|
44
|
+
|
|
45
|
+
def test_permissions_update_unregistered_permission_key(self):
|
|
46
|
+
result = call_action("permissions_update", permissions={"xxx": {}})
|
|
47
|
+
|
|
48
|
+
assert result["missing_permissions"] == ["xxx"]
|
|
49
|
+
assert not result["updated_permissions"]
|
|
50
|
+
|
|
51
|
+
def test_not_string_permission_key(self):
|
|
52
|
+
with pytest.raises(tk.ValidationError, match="Invalid permission key"):
|
|
53
|
+
call_action("permissions_update", permissions={1: {}})
|
|
54
|
+
|
|
55
|
+
def test_not_dict_roles_mapping(self):
|
|
56
|
+
with pytest.raises(tk.ValidationError, match="Invalid permission mapping"):
|
|
57
|
+
call_action("permissions_update", permissions={"perm_1": 1})
|
|
58
|
+
|
|
59
|
+
def test_not_bool_permission_value(self):
|
|
60
|
+
with pytest.raises(tk.ValidationError, match="Invalid permission value"):
|
|
61
|
+
call_action(
|
|
62
|
+
"permissions_update",
|
|
63
|
+
permissions={"perm_1": {"anonymous": "xxx", "authenticated": "yyy"}},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_role_id_not_exists(self):
|
|
67
|
+
with pytest.raises(tk.ValidationError, match="Role xxx doesn't exists"):
|
|
68
|
+
call_action("permissions_update", permissions={"perm_1": {"xxx": True}})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import ckan.plugins.toolkit as tk
|
|
6
|
+
from ckan.tests.helpers import call_action
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.usefixtures("with_plugins", "clean_db")
|
|
10
|
+
class TestPermissionRoleCreate:
|
|
11
|
+
def test_permission_role_create(self):
|
|
12
|
+
result = call_action(
|
|
13
|
+
"permission_role_create",
|
|
14
|
+
id="admin",
|
|
15
|
+
label="Admin",
|
|
16
|
+
description="Admin role",
|
|
17
|
+
)
|
|
18
|
+
assert result["id"] == "admin"
|
|
19
|
+
assert result["label"] == "Admin"
|
|
20
|
+
assert result["description"] == "Admin role"
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize("field", ["id", "label", "description"])
|
|
23
|
+
def test_permission_role_create_missing_required_fields(self, field):
|
|
24
|
+
data = {"id": "admin", "label": "Admin", "description": "Admin role"}
|
|
25
|
+
data.pop(field)
|
|
26
|
+
|
|
27
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
28
|
+
call_action("permission_role_create", **data)
|
|
29
|
+
|
|
30
|
+
assert e.value.error_dict[field] == ["Missing value"]
|
|
31
|
+
|
|
32
|
+
def test_permission_role_create_duplicate_id(self):
|
|
33
|
+
call_action(
|
|
34
|
+
"permission_role_create",
|
|
35
|
+
id="admin",
|
|
36
|
+
label="Admin",
|
|
37
|
+
description="Admin role",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
41
|
+
call_action(
|
|
42
|
+
"permission_role_create",
|
|
43
|
+
id="admin",
|
|
44
|
+
label="Another Admin",
|
|
45
|
+
description="Another admin role",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert e.value.error_dict["id"] == ["Role admin is already exists"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.usefixtures("with_plugins", "clean_db")
|
|
52
|
+
class TestPermissionRoleDelete:
|
|
53
|
+
def test_permission_role_delete(self):
|
|
54
|
+
# Create a role first
|
|
55
|
+
call_action(
|
|
56
|
+
"permission_role_create",
|
|
57
|
+
id="custom_role",
|
|
58
|
+
label="Custom Role",
|
|
59
|
+
description="Custom role for testing",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
call_action("permission_role_delete", id="custom_role")
|
|
63
|
+
|
|
64
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
65
|
+
call_action("permission_role_delete", id="xxx")
|
|
66
|
+
|
|
67
|
+
assert e.value.error_dict["id"] == ["Role xxx doesn't exists"]
|
|
68
|
+
|
|
69
|
+
def test_permission_role_delete_missing_id(self):
|
|
70
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
71
|
+
call_action("permission_role_delete")
|
|
72
|
+
|
|
73
|
+
assert e.value.error_dict["id"] == ["Missing value"]
|
|
74
|
+
|
|
75
|
+
def test_permission_role_delete_nonexistent_role(self):
|
|
76
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
77
|
+
call_action("permission_role_delete", id="xxx")
|
|
78
|
+
|
|
79
|
+
assert e.value.error_dict["id"] == ["Role xxx doesn't exists"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.usefixtures("with_plugins", "clean_db")
|
|
83
|
+
class TestPermissionRoleUpdate:
|
|
84
|
+
def test_permission_role_update(self, test_role: dict[str, Any]):
|
|
85
|
+
result = call_action("permission_role_update", id=test_role["id"], description="New description")
|
|
86
|
+
|
|
87
|
+
assert result["id"] == test_role["id"]
|
|
88
|
+
assert result["description"] == "New description"
|
|
89
|
+
|
|
90
|
+
def test_permission_role_update_missing_id(self):
|
|
91
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
92
|
+
call_action("permission_role_update")
|
|
93
|
+
|
|
94
|
+
assert e.value.error_dict["id"] == ["Missing value"]
|
|
95
|
+
|
|
96
|
+
def test_permission_role_update_nonexistent_role(self):
|
|
97
|
+
with pytest.raises(tk.ValidationError) as e:
|
|
98
|
+
call_action("permission_role_update", id="xxx")
|
|
99
|
+
|
|
100
|
+
assert e.value.error_dict["id"] == ["Role xxx doesn't exists"]
|
|
101
|
+
|
|
102
|
+
def test_permission_role_update_cant_update_label(self, test_role: dict[str, Any]):
|
|
103
|
+
result = call_action(
|
|
104
|
+
"permission_role_update",
|
|
105
|
+
id=test_role["id"],
|
|
106
|
+
label="XXX",
|
|
107
|
+
description="New description",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert test_role["label"] == result["label"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from pytest_factoryboy import register
|
|
7
|
+
|
|
8
|
+
from ckan.tests import factories
|
|
9
|
+
|
|
10
|
+
import ckanext.permissions.model as perm_model
|
|
11
|
+
from ckanext.permissions import utils as perm_utils
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture()
|
|
17
|
+
def clean_db(reset_db, migrate_db_for):
|
|
18
|
+
reset_db()
|
|
19
|
+
migrate_db_for("permissions")
|
|
20
|
+
|
|
21
|
+
perm_utils.ensure_default_roles()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@register(_name="test_role")
|
|
25
|
+
class RoleFactory(factories.CKANFactory):
|
|
26
|
+
class Meta:
|
|
27
|
+
model = perm_model.Role
|
|
28
|
+
action = "permission_role_create"
|
|
29
|
+
|
|
30
|
+
id = "creator"
|
|
31
|
+
label = "Creator"
|
|
32
|
+
description = factory.LazyFunction(lambda: fake.sentence(nb_words=5))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@register(_name="dataset")
|
|
36
|
+
class DatasetFactory(factories.Dataset):
|
|
37
|
+
owner_org = factory.LazyFunction(lambda: OrganizationFactory()["id"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@register(_name="organization")
|
|
41
|
+
class OrganizationFactory(factories.Organization):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@register(_name="sysadmin")
|
|
46
|
+
class SysadminFactory(factories.SysadminWithToken):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@register(_name="user")
|
|
51
|
+
class UserFactory(factories.UserWithToken):
|
|
52
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import ckan.plugins.toolkit as tk
|
|
4
|
+
from ckan.tests.helpers import call_action
|
|
5
|
+
|
|
6
|
+
import ckanext.permissions.model as perm_model
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.usefixtures("with_plugins", "clean_db")
|
|
10
|
+
class TestRoleAutoassignment:
|
|
11
|
+
def test_assign_user_role_on_create(self, user):
|
|
12
|
+
"""Test if a role is assigned to a user when it's created"""
|
|
13
|
+
roles = tk.h.get_user_roles(user["id"])
|
|
14
|
+
assert roles == ["authenticated"]
|
|
15
|
+
|
|
16
|
+
def test_user_roles_deleted_with_role(self, user, test_role):
|
|
17
|
+
"""Test if user roles are deleted when the role is deleted"""
|
|
18
|
+
perm_model.UserRole.create(user["id"], test_role["id"])
|
|
19
|
+
|
|
20
|
+
result = tk.h.get_user_roles(user["id"])
|
|
21
|
+
assert "authenticated" in result
|
|
22
|
+
assert "creator" in result
|
|
23
|
+
|
|
24
|
+
call_action("permission_role_delete", id=test_role["id"])
|
|
25
|
+
|
|
26
|
+
assert tk.h.get_user_roles(user["id"]) == ["authenticated"]
|