Skip to content

第五站练习答案 ​

返回纵览 | 专题一 | 专题二

python
# TicketAdmin 中加入
def get_queryset(self, request):
    queryset = super().get_queryset(request).select_related("assignee")
    if request.user.is_superuser:
        return queryset
    return queryset.filter(assignee=request.user)

def has_delete_permission(self, request, obj=None):
    if obj is not None and obj.status == Ticket.Status.CLOSED:
        return request.user.is_superuser
    return super().has_delete_permission(request, obj)
python
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse


class TicketPermissionTests(TestCase):
    def test_user_cannot_edit_another_users_ticket(self):
        User = get_user_model()
        owner = User.objects.create_user("owner", password="secret-test-pass")
        intruder = User.objects.create_user("intruder", password="secret-test-pass")
        ticket = Ticket.objects.create(title="Private", status="claimed", assignee=owner)
        self.client.force_login(intruder)
        response = self.client.post(
            reverse("tickets:edit", args=[ticket.pk]), {"title": "stolen", "priority": 3}
        )
        self.assertEqual(response.status_code, 404)

专题二核对 ​

  • 关闭服务在同一事务写入 closed_by、closed_at 与状态;审计字段由服务端身份生成,不接受表单覆盖。
  • 每次高价值请求重新检查当前权限;撤权若要求立即生效,则让会话携带权限版本并在版本变化时强制重新认证。
  • Admin action 逐项调用同一授权服务,累计成功、无权与冲突数量,再通过 message_user() 汇总;不要用一次裸 queryset.update() 绕开规则。

深水边界 ​

Admin 的 UI 限制不是最终授权。模型保存、服务层和数据库约束仍要守住不变量,因为脚本、API 和任务可以绕开 Admin。

开放练习(不提供答案) ​

为“关闭工单”建立独立 permission,并比较 403 与隐藏资源时返回 404 的审计和用户体验取舍。

继续第六站

加码题解:表单、身份与对象权限 ​

python
def can_edit(ticket, user):
    return user.is_authenticated and (
        user.is_staff or ticket.owner_id == user.id
    )

关键推理 ​

身份认证和对象授权分开表达,Admin、API、任务都可以复用同一规则。

可替代方案 ​

高风险操作可再加独立 permission、审计事件和数据库约束。

典型错误 ​

只依赖表单字段或前端按钮控制权限;批量 action 绕过逐项授权会产生越权。

复盘时请把状态码/事务/权限/资源清理中的至少一项写成自动化测试,而不是只截图成功页面。

用心记录每一步成长