openedx-authz 1.24.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 (80) hide show
  1. openedx_authz/__init__.py +10 -0
  2. openedx_authz/admin.py +156 -0
  3. openedx_authz/api/__init__.py +11 -0
  4. openedx_authz/api/data.py +1607 -0
  5. openedx_authz/api/permissions.py +66 -0
  6. openedx_authz/api/roles.py +664 -0
  7. openedx_authz/api/users.py +538 -0
  8. openedx_authz/api/utils.py +123 -0
  9. openedx_authz/apps.py +46 -0
  10. openedx_authz/authz/__init__.py +33 -0
  11. openedx_authz/authz/schema/course_permissions.yaml +368 -0
  12. openedx_authz/authz/schema/course_roles.yaml +165 -0
  13. openedx_authz/authz/schema/library_permissions.yaml +115 -0
  14. openedx_authz/authz/schema/library_roles.yaml +74 -0
  15. openedx_authz/conf/locale/config.yaml +85 -0
  16. openedx_authz/constants/__init__.py +7 -0
  17. openedx_authz/constants/permissions.py +254 -0
  18. openedx_authz/constants/roles.py +216 -0
  19. openedx_authz/data.py +115 -0
  20. openedx_authz/engine/__init__.py +0 -0
  21. openedx_authz/engine/adapter.py +149 -0
  22. openedx_authz/engine/apps.py +23 -0
  23. openedx_authz/engine/config/authz.policy +190 -0
  24. openedx_authz/engine/config/model.conf +95 -0
  25. openedx_authz/engine/enforcer.py +264 -0
  26. openedx_authz/engine/filter.py +80 -0
  27. openedx_authz/engine/matcher.py +52 -0
  28. openedx_authz/engine/utils.py +648 -0
  29. openedx_authz/handlers.py +386 -0
  30. openedx_authz/management/__init__.py +0 -0
  31. openedx_authz/management/commands/__init__.py +0 -0
  32. openedx_authz/management/commands/authz_migrate_course_authoring.py +100 -0
  33. openedx_authz/management/commands/authz_rollback_course_authoring.py +106 -0
  34. openedx_authz/management/commands/enforcement.py +269 -0
  35. openedx_authz/management/commands/load_policies.py +144 -0
  36. openedx_authz/migrations/0001_add_casbin_dependency.py +25 -0
  37. openedx_authz/migrations/0002_initial.py +78 -0
  38. openedx_authz/migrations/0003_usersubject.py +42 -0
  39. openedx_authz/migrations/0004_contentlibraryscope.py +43 -0
  40. openedx_authz/migrations/0005_policycachecontrol.py +21 -0
  41. openedx_authz/migrations/0006_migrate_legacy_permissions.py +61 -0
  42. openedx_authz/migrations/0007_coursescope.py +44 -0
  43. openedx_authz/migrations/0008_authzcourseauthoringmigrationrun.py +78 -0
  44. openedx_authz/migrations/0009_roleassignmentaudit.py +76 -0
  45. openedx_authz/migrations/0010_scope_external_key.py +18 -0
  46. openedx_authz/migrations/__init__.py +3 -0
  47. openedx_authz/models/__init__.py +21 -0
  48. openedx_authz/models/authz_migration.py +170 -0
  49. openedx_authz/models/core.py +397 -0
  50. openedx_authz/models/engine.py +51 -0
  51. openedx_authz/models/scopes.py +118 -0
  52. openedx_authz/models/subjects.py +47 -0
  53. openedx_authz/rest_api/__init__.py +0 -0
  54. openedx_authz/rest_api/data.py +87 -0
  55. openedx_authz/rest_api/decorators.py +77 -0
  56. openedx_authz/rest_api/urls.py +9 -0
  57. openedx_authz/rest_api/utils.py +183 -0
  58. openedx_authz/rest_api/v1/__init__.py +0 -0
  59. openedx_authz/rest_api/v1/fields.py +39 -0
  60. openedx_authz/rest_api/v1/filters.py +56 -0
  61. openedx_authz/rest_api/v1/paginators.py +11 -0
  62. openedx_authz/rest_api/v1/permissions.py +412 -0
  63. openedx_authz/rest_api/v1/serializers.py +533 -0
  64. openedx_authz/rest_api/v1/urls.py +24 -0
  65. openedx_authz/rest_api/v1/views.py +1497 -0
  66. openedx_authz/schema/__init__.py +0 -0
  67. openedx_authz/schema/authz-schema-v1.json +399 -0
  68. openedx_authz/settings/__init__.py +0 -0
  69. openedx_authz/settings/common.py +61 -0
  70. openedx_authz/settings/production.py +14 -0
  71. openedx_authz/settings/test.py +84 -0
  72. openedx_authz/templates/openedx_authz/base.html +26 -0
  73. openedx_authz/urls.py +11 -0
  74. openedx_authz/utils.py +118 -0
  75. openedx_authz-1.24.0.dist-info/METADATA +170 -0
  76. openedx_authz-1.24.0.dist-info/RECORD +80 -0
  77. openedx_authz-1.24.0.dist-info/WHEEL +5 -0
  78. openedx_authz-1.24.0.dist-info/entry_points.txt +5 -0
  79. openedx_authz-1.24.0.dist-info/licenses/LICENSE +661 -0
  80. openedx_authz-1.24.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ """
2
+ Open edX AuthZ provides the architecture and foundations of the authorization framework.
3
+ """
4
+
5
+ import os
6
+ from importlib.metadata import version as get_version
7
+
8
+ ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
9
+
10
+ __version__ = get_version("openedx-authz")
openedx_authz/admin.py ADDED
@@ -0,0 +1,156 @@
1
+ """Admin configuration for openedx_authz."""
2
+
3
+ import json
4
+
5
+ from casbin_adapter.models import CasbinRule
6
+ from django import forms
7
+ from django.contrib import admin
8
+ from django.utils.html import format_html
9
+
10
+ from openedx_authz.api.data import ContentLibraryData, CourseOverviewData
11
+ from openedx_authz.models import AuthzCourseAuthoringMigrationRun, ExtendedCasbinRule
12
+ from openedx_authz.models.core import RoleAssignmentAudit
13
+
14
+
15
+ def pretty_json(value) -> str:
16
+ """Return an indented JSON representation of a value."""
17
+ if value is None:
18
+ return "-"
19
+ try:
20
+ formatted = json.dumps(value, indent=2, ensure_ascii=False)
21
+ except (TypeError, ValueError):
22
+ return str(value)
23
+ return format_html("<pre>{}</pre>", formatted)
24
+
25
+
26
+ class CasbinRuleForm(forms.ModelForm):
27
+ """Custom form for CasbinRule to make v3, v4, v5 fields optional."""
28
+
29
+ class Meta:
30
+ """Meta class for CasbinRuleForm."""
31
+
32
+ model = CasbinRule
33
+ fields = "__all__"
34
+
35
+ def __init__(self, *args, **kwargs):
36
+ """Initialize CasbinRuleForm."""
37
+ super().__init__(*args, **kwargs)
38
+ # Make v2, v3, v4, v5 optional in the form
39
+ # These fields are not always required depending on the policy type
40
+ self.fields["v2"].required = False
41
+ self.fields["v3"].required = False
42
+ self.fields["v4"].required = False
43
+ self.fields["v5"].required = False
44
+
45
+
46
+ class ExtendedCasbinRuleInline(admin.StackedInline):
47
+ """Inline admin for ExtendedCasbinRule to display additional metadata."""
48
+
49
+ model = ExtendedCasbinRule
50
+ extra = 0
51
+ fields = ("casbin_rule_key", "scope", "subject", "description", "metadata", "created_at", "updated_at")
52
+ readonly_fields = ("casbin_rule_key", "scope", "subject", "created_at", "updated_at")
53
+ can_delete = False
54
+
55
+
56
+ @admin.register(CasbinRule)
57
+ class CasbinRuleAdmin(admin.ModelAdmin):
58
+ """Admin for CasbinRule to display additional metadata."""
59
+
60
+ form = CasbinRuleForm
61
+ list_display = ("id", "ptype", "v0", "v1", "v2", "v3", "v4", "v5")
62
+ search_fields = ("ptype", "v0", "v1", "v2", "v3", "v4", "v5")
63
+ list_filter = ("ptype",)
64
+ # TODO: In a future, possibly we should only show an inline for the rules that
65
+ # have an extended rule, and show the subject and scope information in detail.
66
+ inlines = [ExtendedCasbinRuleInline]
67
+
68
+
69
+ @admin.register(AuthzCourseAuthoringMigrationRun)
70
+ class AuthzCourseAuthoringMigrationRunAdmin(admin.ModelAdmin):
71
+ """Admin for AuthzCourseAuthoringMigrationRun to display additional metadata."""
72
+
73
+ list_display = ("id", "scope_type", "scope_key", "migration_type", "status", "created_at", "updated_at")
74
+ search_fields = ("scope_type", "scope_key", "migration_type", "status")
75
+ list_filter = ("scope_type", "migration_type", "status")
76
+ readonly_fields = (
77
+ "scope_type",
78
+ "scope_key",
79
+ "migration_type",
80
+ "status",
81
+ "pretty_metadata",
82
+ "completed_at",
83
+ "created_at",
84
+ "updated_at",
85
+ )
86
+ fields = readonly_fields
87
+
88
+ @admin.display(description="Metadata")
89
+ def pretty_metadata(self, obj):
90
+ """Return formatted JSON for the metadata field."""
91
+ return pretty_json(obj.metadata)
92
+
93
+
94
+ class ScopeTypeFilter(admin.SimpleListFilter):
95
+ """Filter audit records by scope type (content library, course, etc.)."""
96
+
97
+ title = "scope type"
98
+ parameter_name = "scope_type"
99
+
100
+ def lookups(self, request, model_admin):
101
+ """
102
+ Return the available scope type choices.
103
+
104
+ Audit records are independent from live Casbin tables and scope objects:
105
+ there are no FK references to filter on. The namespace prefix in the
106
+ stored ``scope`` string (e.g. ``lib^``, ``course-v1^``) is the only
107
+ available signal for categorizing records by scope type.
108
+ """
109
+ return [
110
+ (ContentLibraryData.NAMESPACE, "Content Library"),
111
+ (CourseOverviewData.NAMESPACE, "Course"),
112
+ ]
113
+
114
+ def queryset(self, request, queryset):
115
+ """Filter the queryset by scope namespace prefix."""
116
+ if self.value():
117
+ return queryset.for_scope_namespace(self.value())
118
+ return queryset
119
+
120
+
121
+ @admin.register(RoleAssignmentAudit)
122
+ class RoleAssignmentAuditAdmin(admin.ModelAdmin):
123
+ """Read-only admin for the role assignment audit log."""
124
+
125
+ list_display = ("operation", "display_subject", "display_role", "display_scope", "actor_id", "timestamp")
126
+ list_filter = ("operation", ScopeTypeFilter)
127
+ search_fields = ("subject", "role", "scope")
128
+ date_hierarchy = "timestamp"
129
+ readonly_fields = ("operation", "subject", "role", "scope", "actor_id", "timestamp")
130
+
131
+ @admin.display(description="subject")
132
+ def display_subject(self, obj):
133
+ """Subject key without the namespace prefix."""
134
+ return obj.subject_display
135
+
136
+ @admin.display(description="role")
137
+ def display_role(self, obj):
138
+ """Role name without the namespace prefix."""
139
+ return obj.role_display
140
+
141
+ @admin.display(description="scope")
142
+ def display_scope(self, obj):
143
+ """Scope key without the namespace prefix."""
144
+ return obj.scope_display
145
+
146
+ def has_add_permission(self, request):
147
+ """Audit records are created by the system only."""
148
+ return False
149
+
150
+ def has_change_permission(self, request, obj=None):
151
+ """Audit records must not be modified after creation."""
152
+ return False
153
+
154
+ def has_delete_permission(self, request, obj=None):
155
+ """Audit records must not be deleted through the admin."""
156
+ return False
@@ -0,0 +1,11 @@
1
+ """Public API for the Open edX AuthZ framework.
2
+
3
+ This module provides a public API as part of the Open edX AuthZ framework. This
4
+ is part of the Open edX Layer used to abstract the authorization engine and
5
+ provide a simpler interface for other services in the Open edX ecosystem.
6
+ """
7
+
8
+ from openedx_authz.api.data import *
9
+ from openedx_authz.api.permissions import *
10
+ from openedx_authz.api.roles import *
11
+ from openedx_authz.api.users import *