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.
Files changed (39) hide show
  1. ckanext/__init__.py +9 -0
  2. ckanext/permissions/__init__.py +0 -0
  3. ckanext/permissions/cli.py +80 -0
  4. ckanext/permissions/const.py +10 -0
  5. ckanext/permissions/helpers.py +51 -0
  6. ckanext/permissions/implementation/__init__.py +5 -0
  7. ckanext/permissions/implementation/permission_labels.py +45 -0
  8. ckanext/permissions/logic/__init__.py +0 -0
  9. ckanext/permissions/logic/action.py +113 -0
  10. ckanext/permissions/logic/auth.py +82 -0
  11. ckanext/permissions/logic/schema.py +54 -0
  12. ckanext/permissions/logic/validators.py +112 -0
  13. ckanext/permissions/migration/permissions/alembic.ini +74 -0
  14. ckanext/permissions/migration/permissions/env.py +85 -0
  15. ckanext/permissions/migration/permissions/script.py.mako +24 -0
  16. ckanext/permissions/migration/permissions/versions/a849104ccfdc_init_tables.py +69 -0
  17. ckanext/permissions/model.py +148 -0
  18. ckanext/permissions/plugin.py +67 -0
  19. ckanext/permissions/tests/__init__.py +0 -0
  20. ckanext/permissions/tests/actions/test_permission.py +68 -0
  21. ckanext/permissions/tests/actions/test_role.py +110 -0
  22. ckanext/permissions/tests/conftest.py +52 -0
  23. ckanext/permissions/tests/test_autoassign.py +26 -0
  24. ckanext/permissions/tests/test_helpers.py +67 -0
  25. ckanext/permissions/tests/test_permission_labels.py +28 -0
  26. ckanext/permissions/tests/test_utils.py +275 -0
  27. ckanext/permissions/tests/test_validators.py +98 -0
  28. ckanext/permissions/types.py +34 -0
  29. ckanext/permissions/utils.py +197 -0
  30. ckanext/permissions_manager/__init__.py +0 -0
  31. ckanext/permissions_manager/helpers.py +23 -0
  32. ckanext/permissions_manager/plugin.py +48 -0
  33. ckanext/permissions_manager/views.py +351 -0
  34. ckanext_permissions-0.2.0.dist-info/METADATA +104 -0
  35. ckanext_permissions-0.2.0.dist-info/RECORD +39 -0
  36. ckanext_permissions-0.2.0.dist-info/WHEEL +5 -0
  37. ckanext_permissions-0.2.0.dist-info/entry_points.txt +6 -0
  38. ckanext_permissions-0.2.0.dist-info/licenses/LICENSE +661 -0
  39. ckanext_permissions-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,23 @@
1
+ from ckan.lib import munge
2
+
3
+ from ckanext.permissions.utils import get_registered_roles
4
+
5
+
6
+ def permission_munge_string(value: str) -> str:
7
+ """Munge a string using CKAN's munge_name function.
8
+
9
+ Args:
10
+ value: The string to munge
11
+
12
+ Returns:
13
+ The munged string
14
+ """
15
+ return munge.munge_name(value)
16
+
17
+
18
+ def permission_get_registered_roles_options() -> list[dict[str, str]]:
19
+ return [
20
+ {"value": role_id, "text": role_label}
21
+ for role_id, role_label in get_registered_roles().items()
22
+ if role_id not in ("administrator", "anonymous")
23
+ ]
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import ckan.plugins as p
4
+ import ckan.plugins.toolkit as tk
5
+ import ckan.types as types
6
+
7
+
8
+ @tk.blanket.blueprints
9
+ @tk.blanket.helpers
10
+ class PermissionsManagerPlugin(p.SingletonPlugin):
11
+ p.implements(p.IConfigurer)
12
+ p.implements(p.ISignal)
13
+
14
+ # IConfigurer
15
+
16
+ def update_config(self, config_):
17
+ tk.add_template_directory(config_, "templates")
18
+ tk.add_public_directory(config_, "public")
19
+ tk.add_resource("assets", "permissions_manager")
20
+
21
+ # ISignal
22
+
23
+ def get_signal_subscriptions(self) -> types.SignalMapping:
24
+ return {
25
+ tk.signals.ckanext.signal("ap_main:collect_config_sections"): [self.collect_config_sections_subs],
26
+ }
27
+
28
+ @staticmethod
29
+ def collect_config_sections_subs(sender: None):
30
+ return {
31
+ "name": "Permissions",
32
+ "configs": [
33
+ {
34
+ "name": "Permissions manager",
35
+ "blueprint": "perm_manager.permission_list",
36
+ "info": "Manage portal permissions",
37
+ },
38
+ {
39
+ "name": "Roles manager",
40
+ "blueprint": "perm_manager.role_list",
41
+ "info": "Manage portal roles",
42
+ },
43
+ {
44
+ "name": "User roles list",
45
+ "blueprint": "perm_manager.user_roles_list",
46
+ },
47
+ ],
48
+ }
@@ -0,0 +1,351 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Union
5
+
6
+ from flask import Blueprint, Response
7
+ from flask.views import MethodView
8
+
9
+ import ckan.model as model
10
+ import ckan.plugins.toolkit as tk
11
+ import ckan.types as types
12
+ from ckan.lib.helpers import Page
13
+
14
+ from ckanext.permissions import model as perm_model
15
+ from ckanext.permissions import utils
16
+
17
+ log = logging.getLogger(__name__)
18
+ perm_manager = Blueprint("perm_manager", __name__, url_prefix="/permissions")
19
+
20
+
21
+ USER_ROLES_PER_PAGE = 10
22
+
23
+
24
+ @perm_manager.before_request
25
+ def before_request() -> None:
26
+ try:
27
+ tk.check_access("sysadmin", {"user": tk.current_user.name})
28
+ except tk.NotAuthorized:
29
+ tk.abort(403, tk._("Need to be system administrator to administer"))
30
+
31
+
32
+ class PermissionManagerView(MethodView):
33
+ def get(self) -> Union[str, Response]:
34
+ return tk.render(
35
+ "perm_manager/list.html",
36
+ extra_vars={
37
+ "permission_groups": utils.get_permission_groups(),
38
+ },
39
+ )
40
+
41
+ def post(self) -> Response:
42
+ try:
43
+ tk.get_action("permissions_update")({}, {"permissions": self._get_permissions()})
44
+ except tk.ValidationError as e:
45
+ tk.h.flash_error(str(e))
46
+ return tk.redirect_to("perm_manager.permission_list")
47
+
48
+ tk.h.flash_success("Permissions updated")
49
+
50
+ return tk.redirect_to("perm_manager.permission_list")
51
+
52
+ def _get_permissions(self) -> dict[str, dict[str, bool]]:
53
+ permissions = {}
54
+
55
+ for key in tk.request.form.keys():
56
+ if "|" not in key:
57
+ continue
58
+
59
+ values = tk.request.form.getlist(key)
60
+ permission, role_id = key.split("|")
61
+
62
+ if permission not in permissions:
63
+ permissions[permission] = {}
64
+
65
+ permissions[permission][role_id] = "set" in values
66
+
67
+ return permissions
68
+
69
+
70
+ class RoleManagerView(MethodView):
71
+ def get(self) -> Union[str, Response]:
72
+ return tk.render(
73
+ "perm_manager/role_list.html",
74
+ extra_vars={
75
+ "roles": sorted(perm_model.Role.all(), key=lambda x: x["label"]),
76
+ },
77
+ )
78
+
79
+
80
+ class RoleAdd(MethodView):
81
+ def get(self) -> Union[str, Response]:
82
+ return tk.render(
83
+ "perm_manager/add_role.html",
84
+ extra_vars={"errors": {}, "data": {}},
85
+ )
86
+
87
+ def post(self) -> Union[str, Response]:
88
+ payload = dict(tk.request.form)
89
+
90
+ tk.get_or_bust(payload, ["id", "label", "description"])
91
+
92
+ try:
93
+ tk.get_action("permission_role_create")({}, payload)
94
+ except tk.NotAuthorized as e:
95
+ return tk.abort(403, str(e))
96
+ except tk.ValidationError as e:
97
+ return tk.render(
98
+ "perm_manager/add_role.html",
99
+ extra_vars={"errors": e.error_dict, "data": payload},
100
+ )
101
+
102
+ tk.h.flash_success("Role has been created")
103
+
104
+ return tk.redirect_to("perm_manager.role_list")
105
+
106
+
107
+ class RoleDelete(MethodView):
108
+ def post(self) -> Response:
109
+ payload = dict(tk.request.form)
110
+
111
+ tk.get_or_bust(payload, ["id"])
112
+
113
+ try:
114
+ tk.get_action("permission_role_delete")({}, payload)
115
+ except tk.ValidationError as e:
116
+ tk.h.flash_error(str(e))
117
+ else:
118
+ tk.h.flash_success("Role has been deleted")
119
+
120
+ return tk.redirect_to("perm_manager.role_list")
121
+
122
+
123
+ class RoleEdit(MethodView):
124
+ def get(self, role_id: str) -> Union[str, Response]:
125
+ return tk.render(
126
+ "perm_manager/edit_role.html",
127
+ extra_vars={"role": perm_model.Role.get(role_id), "errors": {}, "data": {}},
128
+ )
129
+
130
+ def post(self, role_id: str) -> Union[str, Response]:
131
+ payload = dict(tk.request.form)
132
+
133
+ tk.get_or_bust(payload, "description")
134
+
135
+ try:
136
+ tk.get_action("permission_role_update")(
137
+ {},
138
+ {
139
+ "id": role_id,
140
+ "description": payload["description"],
141
+ },
142
+ )
143
+ except tk.ValidationError as e:
144
+ return tk.render(
145
+ "perm_manager/edit_role.html",
146
+ extra_vars={
147
+ "role": perm_model.Role.get(role_id),
148
+ "errors": e.error_dict,
149
+ "data": payload,
150
+ },
151
+ )
152
+
153
+ tk.h.flash_success("Role has been updated")
154
+
155
+ return tk.redirect_to("perm_manager.role_list")
156
+
157
+
158
+ class BaseUserRolesList(MethodView):
159
+ def _get_user_with_roles(self, scope: str = "global", scope_id: str | None = None) -> list[dict[str, Any]]:
160
+ """
161
+ Get all active users and their roles.
162
+ """
163
+ users = self._get_active_users()
164
+ result: list[dict[str, Any]] = []
165
+
166
+ for user in users:
167
+ result.append(
168
+ {
169
+ "id": user.id,
170
+ "display_name": user.display_name,
171
+ "roles": tk.h.get_user_roles(user.id, scope, scope_id),
172
+ }
173
+ )
174
+
175
+ return self._apply_filters(result)
176
+
177
+ def _get_active_users(self) -> list[model.User]:
178
+ """
179
+ Get all active users and sort them by display name.
180
+ """
181
+ return sorted(
182
+ (model.Session.query(model.User).filter(model.User.state == model.State.ACTIVE).all()),
183
+ key=lambda x: x.display_name,
184
+ )
185
+
186
+ def _apply_filters(self, users: list[dict[str, Any]]) -> list[dict[str, Any]]:
187
+ """
188
+ Apply filters based on username and role.
189
+ """
190
+ q = tk.request.args.get("q", "").strip()
191
+ role_filter = tk.request.args.get("role", "").strip()
192
+
193
+ if q:
194
+ users = [user for user in users if q.lower() in user["display_name"].lower() or q.lower() in user["roles"]]
195
+
196
+ if role_filter:
197
+ users = [user for user in users if role_filter in user["roles"]]
198
+
199
+ return users
200
+
201
+
202
+ class UserRolesList(BaseUserRolesList):
203
+ def get(self) -> Union[str, Response]:
204
+ users = self._get_user_with_roles()
205
+ page = Page(
206
+ collection=users,
207
+ page=tk.h.get_page_number(tk.request.args),
208
+ url=tk.h.pager_url,
209
+ item_count=len(users),
210
+ items_per_page=int(tk.request.args.get("limit", USER_ROLES_PER_PAGE)),
211
+ )
212
+ return tk.render("perm_manager/user_roles_list.html", extra_vars={"page": page})
213
+
214
+
215
+ class OrganizationUserRolesList(BaseUserRolesList):
216
+ def get(self, org_id: str) -> Union[str, Response]:
217
+ users = self._get_user_with_roles(scope="organization", scope_id=org_id)
218
+ org_dict = _get_org_dict(org_id)
219
+
220
+ def _pager_url(**kwargs):
221
+ return tk.h.url_for("perm_manager.organization_user_roles_list", org_id=org_id, **kwargs)
222
+
223
+ page = Page(
224
+ collection=users,
225
+ page=tk.h.get_page_number(tk.request.args),
226
+ url=_pager_url,
227
+ item_count=len(users),
228
+ items_per_page=int(tk.request.args.get("limit", USER_ROLES_PER_PAGE)),
229
+ )
230
+
231
+ return tk.render(
232
+ "perm_manager/organization/user_roles_list.html",
233
+ extra_vars={
234
+ "group_dict": org_dict,
235
+ "group_type": "organization",
236
+ "page": page,
237
+ },
238
+ )
239
+
240
+
241
+ class EditUserRole(MethodView):
242
+ def __init__(self):
243
+ self.schema = {
244
+ "roles": [tk.get_validator(validator) for validator in "not_missing list_of_strings roles_exists".split()]
245
+ }
246
+
247
+ def get(self, user_id: str) -> Union[str, Response]:
248
+ user = model.User.get(user_id)
249
+
250
+ if not user:
251
+ return tk.abort(404, "User not found")
252
+
253
+ return tk.render(
254
+ "perm_manager/edit_user_roles.html",
255
+ extra_vars={
256
+ "user": user,
257
+ "data": {"roles": ",".join(tk.h.get_user_roles(user.id))},
258
+ "errors": {},
259
+ },
260
+ )
261
+
262
+ def post(self, user_id: str) -> Union[str, Response]:
263
+ return self._update_user_roles(user_id, "global")
264
+
265
+ def _update_user_roles(self, user_id: str, scope: str, scope_id: str | None = None) -> Union[str, Response]:
266
+ payload = {"roles": tk.request.form.getlist("roles")}
267
+
268
+ user = model.User.get(user_id)
269
+
270
+ if not user:
271
+ tk.abort(404, "User not found")
272
+
273
+ data, errors = tk.navl_validate(payload, self.schema)
274
+
275
+ if errors:
276
+ return tk.render(
277
+ "perm_manager/edit_user_roles.html",
278
+ extra_vars={"user": user, "data": data, "errors": errors},
279
+ )
280
+
281
+ perm_model.UserRole.clear_user_roles(user.id, scope, scope_id)
282
+
283
+ for role in data["roles"]:
284
+ perm_model.UserRole.create(user_id=user.id, role=role, scope=scope, scope_id=scope_id)
285
+
286
+ model.Session.commit()
287
+
288
+ tk.h.flash_success("User roles updated")
289
+
290
+ return (
291
+ tk.redirect_to("perm_manager.user_roles_list")
292
+ if scope == "global"
293
+ else tk.redirect_to("perm_manager.organization_user_roles_list", org_id=scope_id)
294
+ )
295
+
296
+
297
+ class OrganizationEditUserRole(EditUserRole):
298
+ def get(self, org_id: str, user_id: str) -> Union[str, Response]:
299
+ user = model.User.get(user_id)
300
+
301
+ if not user:
302
+ return tk.abort(404, "User not found")
303
+
304
+ org_dict = _get_org_dict(org_id)
305
+ scope = "organization"
306
+
307
+ return tk.render(
308
+ "perm_manager/organization/edit_user_roles.html",
309
+ extra_vars={
310
+ "user": user,
311
+ "data": {"roles": ",".join(tk.h.get_user_roles(user.id, scope, org_dict["id"]))},
312
+ "errors": {},
313
+ "group_dict": org_dict,
314
+ "group_type": scope,
315
+ },
316
+ )
317
+
318
+ def post(self, org_id: str, user_id: str) -> Union[str, Response]:
319
+ return self._update_user_roles(user_id, "organization", org_id)
320
+
321
+
322
+ def _get_org_dict(org_id: str) -> dict[str, Any]:
323
+ context = types.Context(user=tk.current_user.name, for_view=True)
324
+
325
+ try:
326
+ return tk.get_action("organization_show")(context, {"id": org_id, "include_datasets": False})
327
+ except (tk.ObjectNotFound, tk.NotAuthorized):
328
+ tk.abort(404, tk._("Organization not found"))
329
+
330
+
331
+ perm_manager.add_url_rule("/manage", view_func=PermissionManagerView.as_view("permission_list"))
332
+
333
+ perm_manager.add_url_rule("/roles", view_func=RoleManagerView.as_view("role_list"))
334
+ perm_manager.add_url_rule("/roles/add", view_func=RoleAdd.as_view("role_add"))
335
+ perm_manager.add_url_rule("/roles/delete", view_func=RoleDelete.as_view("role_delete"))
336
+ perm_manager.add_url_rule("/roles/<role_id>", view_func=RoleEdit.as_view("role_edit"))
337
+
338
+ # organization user roles
339
+ perm_manager.add_url_rule(
340
+ "/organization/user-roles/<org_id>",
341
+ view_func=OrganizationUserRolesList.as_view("organization_user_roles_list"),
342
+ )
343
+ perm_manager.add_url_rule(
344
+ "/organization/user-roles/<org_id>/<user_id>",
345
+ view_func=OrganizationEditUserRole.as_view("organization_edit_user_role"),
346
+ )
347
+
348
+ perm_manager.add_url_rule("/user-roles", view_func=UserRolesList.as_view("user_roles_list"))
349
+ perm_manager.add_url_rule("/user-roles/<user_id>", view_func=EditUserRole.as_view("edit_user_role"))
350
+
351
+ blueprints = [perm_manager]
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: ckanext-permissions
3
+ Version: 0.2.0
4
+ Summary: Permission system for CKAN
5
+ Author-email: DataShades <datashades@linkdigital.com.au>, Oleksandr Cherniavskyi <mutantsan@gmail.com>
6
+ Maintainer-email: DataShades <datashades@linkdigital.com.au>
7
+ License: AGPL
8
+ Project-URL: Homepage, https://github.com/DataShades/ckanext-permissions
9
+ Keywords: CKAN
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest-ckan; extra == "dev"
19
+ Requires-Dist: pytest-toolbelt; extra == "dev"
20
+ Requires-Dist: ckanext-toolbelt; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ [![Tests](https://github.com/DataShades/ckanext-permissions/actions/workflows/test.yml/badge.svg)](https://github.com/DataShades/ckanext-permissions/actions/workflows/test.yml)
24
+
25
+ # ckanext-permissions
26
+
27
+ > [!WARNING]
28
+ > This extension is still under development and not ready for production use.
29
+
30
+ The extension allows you to build a Access Control List (ACL) system within CKAN.
31
+
32
+ ![acl.png](doc/acl.png)
33
+
34
+
35
+ ### Roles
36
+
37
+ The extension has a 3 default roles: `anonymous`, `authenticated` and `administrator`. And allows you to define custom roles.
38
+
39
+ ![roles.png](doc/roles.png)
40
+
41
+ ### Assigning roles to users
42
+
43
+ The extension provides a way to assign roles to users. Roles could be global and scoped to an organization.
44
+
45
+ ![role-assignment.png](doc/role-assignment.png)
46
+
47
+
48
+ ## Requirements
49
+
50
+ Compatibility with core CKAN versions:
51
+
52
+ | CKAN version | Compatible? |
53
+ | --------------- | ------------- |
54
+ | 2.9 and earlier | no |
55
+ | 2.10+ | yes |
56
+ | 2.11+ | yes |
57
+
58
+
59
+ ## Installation
60
+
61
+ Using GIT Clone:
62
+
63
+ 1. Activate your CKAN virtual environment, for example:
64
+
65
+ . /usr/lib/ckan/default/bin/activate
66
+
67
+ 2. Clone the source and install it on the virtualenv
68
+
69
+ git clone https://github.com/DataShades/ckanext-permissions.git
70
+
71
+ cd ckanext-permissions
72
+
73
+ pip install -e .
74
+
75
+ 3. Add `permissions permissions_manager` to the `ckan.plugins` setting in your CKAN
76
+ config file (by default the config file is located at
77
+ `/etc/ckan/default/ckan.ini`).
78
+
79
+ 4. Initialize DB tables:
80
+
81
+ ckan -c PATH_TO_CONFIG db upgrade
82
+
83
+ 5. Initialize default Roles and add Authenticated default role to all existing Users:
84
+
85
+ ckan -c PATH_TO_CONFIG permissions assign-default-user-roles
86
+
87
+ 7. Restart CKAN.
88
+
89
+
90
+ ## Config settings
91
+
92
+ TBD
93
+
94
+
95
+ ## Tests
96
+
97
+ To run the tests, do:
98
+
99
+ pytest --ckan-ini=test.ini --cov=ckanext.permissions
100
+
101
+
102
+ ## License
103
+
104
+ [AGPL](https://www.gnu.org/licenses/agpl-3.0.en.html)
@@ -0,0 +1,39 @@
1
+ ckanext/__init__.py,sha256=bA4GtkniRdq2--6cjASDLRM4gCsX_ysGJkaTMSmIQY8,202
2
+ ckanext/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ ckanext/permissions/cli.py,sha256=A48-pfK2hhLWT2MrOMJwm0NNNe43GGLsbbsXq-8B9sw,2439
4
+ ckanext/permissions/const.py,sha256=KwP1mrgb_SE_n1aEj7pdxC5jxe5rWIxPJPZSSZeJy88,191
5
+ ckanext/permissions/helpers.py,sha256=rIRSFGLT-B_p1bOYPco2FoS_5eEBUnvOrmDrByPr0ck,1334
6
+ ckanext/permissions/model.py,sha256=6VkUklXcte_WsSCPeMhPdBJIXrP_g00NK1X5TyMHHK0,4323
7
+ ckanext/permissions/plugin.py,sha256=hMWhrwpED0hwFFl0lR0vxb5k9w3Qseye3RCqDqK64oA,2098
8
+ ckanext/permissions/types.py,sha256=9MM5GJgnWbW8YZ1xOw-YQsVzGI05hlCUUtVd8O1vcQU,598
9
+ ckanext/permissions/utils.py,sha256=FUOzW7EO5AEJNmZuQ1IdK6dQSSbvhEItaVsyHti4VGo,5300
10
+ ckanext/permissions/implementation/__init__.py,sha256=tJ9Gz2mK8mBPXYmEwwQL8xxtCs491x5H-h8X-ZkWUZg,87
11
+ ckanext/permissions/implementation/permission_labels.py,sha256=mwtjW8HJFXbHeTFcxQ0QsOWqjCLu6Hkw6N56mg5IZAc,1524
12
+ ckanext/permissions/logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ ckanext/permissions/logic/action.py,sha256=eKaq7PQT6t03BFWOatoQPdAvkNKZFBKXjx8EvNv2PEI,3598
14
+ ckanext/permissions/logic/auth.py,sha256=0KQjIcxXbhzG9oZadWUF7YVl5n5TE6YnMCwL5ecKTDc,2452
15
+ ckanext/permissions/logic/schema.py,sha256=u7zIjeMT60Hjcm7_x8Ch6Qjyl7aK7buUE0wqOJmf_wo,1608
16
+ ckanext/permissions/logic/validators.py,sha256=3AQ6t-yp9bcpxLlikDhCgInwpNIKSZ_pu1EnK6Qc-Ok,2498
17
+ ckanext/permissions/migration/permissions/alembic.ini,sha256=TjMs29GvgS6oUMdLfXbw1onzwkRBQ9pSft78UgAAUUE,1812
18
+ ckanext/permissions/migration/permissions/env.py,sha256=LQT2UgozCI1O1gRtHRC9BSk9K3ks8wQxTW_j1pQ65Yw,2228
19
+ ckanext/permissions/migration/permissions/script.py.mako,sha256=8_xgA-gm_OhehnO7CiIijWgnm00ZlszEHtIHrAYFJl0,494
20
+ ckanext/permissions/migration/permissions/versions/a849104ccfdc_init_tables.py,sha256=T8TqpsPELtCRgAZp66SM-z8BnkpJbdAolh-mUaGThco,1585
21
+ ckanext/permissions/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ ckanext/permissions/tests/conftest.py,sha256=S8W4U9VYcOi0RIDw9bctMAjFbmdxnoIuqBfsRYAkd-w,1113
23
+ ckanext/permissions/tests/test_autoassign.py,sha256=U_ZA4hTaERS6uYyvVq6_aMYwh72uXW5geBM-oYuEWoM,900
24
+ ckanext/permissions/tests/test_helpers.py,sha256=vIvoF0Jfy4hE9QaM7tllD0B6iGXR7HpT3tD8Rpl-CPo,2405
25
+ ckanext/permissions/tests/test_permission_labels.py,sha256=CV42TMQwjB5BiSnosD2m455vZCyD60qeGezB4Nc-IAk,914
26
+ ckanext/permissions/tests/test_utils.py,sha256=V7mcswMW-Jhv26-dDcx92GJvhEXJLGFUnDUDTzggSk0,9220
27
+ ckanext/permissions/tests/test_validators.py,sha256=hlhLWyosSi-vY-nWDcMvNDEXaaBZPHRuyjQhvoLZ8so,3499
28
+ ckanext/permissions/tests/actions/test_permission.py,sha256=sqTvF3nbTNAZm1fzmsgUg7udQT_X19PEFcTbgjpbEuw,2540
29
+ ckanext/permissions/tests/actions/test_role.py,sha256=RaZxAQ63pNJel0GtO2fnn1MbeaYW809g0PHQ7cs6ANI,3729
30
+ ckanext/permissions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ ckanext/permissions_manager/helpers.py,sha256=e_DMaAohk_bV-Z1WOsgsk19m8VGcbRiY_XDo_H4MGxY,584
32
+ ckanext/permissions_manager/plugin.py,sha256=ssESyXDbK43p6nINArBGNXWUvxxY9dSFmYk3a2WjhO0,1441
33
+ ckanext/permissions_manager/views.py,sha256=C_nL7nTJpW8NQoWpIBVZqV6sbyRasCGE2aUcEIHfzL0,11270
34
+ ckanext_permissions-0.2.0.dist-info/licenses/LICENSE,sha256=2lWcRAHjsQhqavGNnR30Ymxq3GJ9BaYL_dnfGO_-WFA,34500
35
+ ckanext_permissions-0.2.0.dist-info/METADATA,sha256=dvT8tyEhY4RPdwQzbDSwmPZzjYg0ejLacvly0VOBSsY,2801
36
+ ckanext_permissions-0.2.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
37
+ ckanext_permissions-0.2.0.dist-info/entry_points.txt,sha256=y3oO5e5vSFvReRU-bvsf6TWlKFGEM2XoMqxdjoJmTx0,213
38
+ ckanext_permissions-0.2.0.dist-info/top_level.txt,sha256=5yjNwq-s42weaiMMUuA5lZ45g99ANsfcRBCvac1JMS4,8
39
+ ckanext_permissions-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ [babel.extractors]
2
+ ckan = ckan.lib.extract:extract_ckan
3
+
4
+ [ckan.plugins]
5
+ permissions = ckanext.permissions.plugin:PermissionsPlugin
6
+ permissions_manager = ckanext.permissions_manager.plugin:PermissionsManagerPlugin