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.
- openedx_authz/__init__.py +10 -0
- openedx_authz/admin.py +156 -0
- openedx_authz/api/__init__.py +11 -0
- openedx_authz/api/data.py +1607 -0
- openedx_authz/api/permissions.py +66 -0
- openedx_authz/api/roles.py +664 -0
- openedx_authz/api/users.py +538 -0
- openedx_authz/api/utils.py +123 -0
- openedx_authz/apps.py +46 -0
- openedx_authz/authz/__init__.py +33 -0
- openedx_authz/authz/schema/course_permissions.yaml +368 -0
- openedx_authz/authz/schema/course_roles.yaml +165 -0
- openedx_authz/authz/schema/library_permissions.yaml +115 -0
- openedx_authz/authz/schema/library_roles.yaml +74 -0
- openedx_authz/conf/locale/config.yaml +85 -0
- openedx_authz/constants/__init__.py +7 -0
- openedx_authz/constants/permissions.py +254 -0
- openedx_authz/constants/roles.py +216 -0
- openedx_authz/data.py +115 -0
- openedx_authz/engine/__init__.py +0 -0
- openedx_authz/engine/adapter.py +149 -0
- openedx_authz/engine/apps.py +23 -0
- openedx_authz/engine/config/authz.policy +190 -0
- openedx_authz/engine/config/model.conf +95 -0
- openedx_authz/engine/enforcer.py +264 -0
- openedx_authz/engine/filter.py +80 -0
- openedx_authz/engine/matcher.py +52 -0
- openedx_authz/engine/utils.py +648 -0
- openedx_authz/handlers.py +386 -0
- openedx_authz/management/__init__.py +0 -0
- openedx_authz/management/commands/__init__.py +0 -0
- openedx_authz/management/commands/authz_migrate_course_authoring.py +100 -0
- openedx_authz/management/commands/authz_rollback_course_authoring.py +106 -0
- openedx_authz/management/commands/enforcement.py +269 -0
- openedx_authz/management/commands/load_policies.py +144 -0
- openedx_authz/migrations/0001_add_casbin_dependency.py +25 -0
- openedx_authz/migrations/0002_initial.py +78 -0
- openedx_authz/migrations/0003_usersubject.py +42 -0
- openedx_authz/migrations/0004_contentlibraryscope.py +43 -0
- openedx_authz/migrations/0005_policycachecontrol.py +21 -0
- openedx_authz/migrations/0006_migrate_legacy_permissions.py +61 -0
- openedx_authz/migrations/0007_coursescope.py +44 -0
- openedx_authz/migrations/0008_authzcourseauthoringmigrationrun.py +78 -0
- openedx_authz/migrations/0009_roleassignmentaudit.py +76 -0
- openedx_authz/migrations/0010_scope_external_key.py +18 -0
- openedx_authz/migrations/__init__.py +3 -0
- openedx_authz/models/__init__.py +21 -0
- openedx_authz/models/authz_migration.py +170 -0
- openedx_authz/models/core.py +397 -0
- openedx_authz/models/engine.py +51 -0
- openedx_authz/models/scopes.py +118 -0
- openedx_authz/models/subjects.py +47 -0
- openedx_authz/rest_api/__init__.py +0 -0
- openedx_authz/rest_api/data.py +87 -0
- openedx_authz/rest_api/decorators.py +77 -0
- openedx_authz/rest_api/urls.py +9 -0
- openedx_authz/rest_api/utils.py +183 -0
- openedx_authz/rest_api/v1/__init__.py +0 -0
- openedx_authz/rest_api/v1/fields.py +39 -0
- openedx_authz/rest_api/v1/filters.py +56 -0
- openedx_authz/rest_api/v1/paginators.py +11 -0
- openedx_authz/rest_api/v1/permissions.py +412 -0
- openedx_authz/rest_api/v1/serializers.py +533 -0
- openedx_authz/rest_api/v1/urls.py +24 -0
- openedx_authz/rest_api/v1/views.py +1497 -0
- openedx_authz/schema/__init__.py +0 -0
- openedx_authz/schema/authz-schema-v1.json +399 -0
- openedx_authz/settings/__init__.py +0 -0
- openedx_authz/settings/common.py +61 -0
- openedx_authz/settings/production.py +14 -0
- openedx_authz/settings/test.py +84 -0
- openedx_authz/templates/openedx_authz/base.html +26 -0
- openedx_authz/urls.py +11 -0
- openedx_authz/utils.py +118 -0
- openedx_authz-1.24.0.dist-info/METADATA +170 -0
- openedx_authz-1.24.0.dist-info/RECORD +80 -0
- openedx_authz-1.24.0.dist-info/WHEEL +5 -0
- openedx_authz-1.24.0.dist-info/entry_points.txt +5 -0
- openedx_authz-1.24.0.dist-info/licenses/LICENSE +661 -0
- 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 *
|