Skip to content

Commit e041bac

Browse files
devGregAclaude
andcommitted
feat(authorization): release RBAC tables from dojo state to managed=False shells
Migration 0268_release_rbac_state.py flips the seven RBAC models in dojo's app state to ``managed=False`` via AlterModelOptions. Pairs with ``pro.0049_adopt_rbac_tables`` (Pro creates the same models in its state via SeparateDatabaseAndState with state_operations only). Together the two migrations transfer schema ownership from dojo to pro without running any DDL — the underlying tables are bit-for-bit unchanged, every existing row stays in place, and constraints / indexes are not touched. The model class definitions remain in dojo/authorization/models.py as managed=False shells with related_name="+" on every FK to a dojo-owned model (suppresses reverse-accessor name clashes against Pro's now-managed copies). Track B step DefectDojo#13 simplifies the OS callers that still isinstance-check or import these classes; at that point the shells are deleted entirely and OS-only deployments stop carrying their dead weight. Verified end-to-end: - ``migrate`` applies pro.0049 → dojo.0268 in correct cross-app order (run_before on the pro migration enforces it). - ``select count(*) from dojo_role/dojo_global_role/dojo_product_member`` unchanged after migration; no rows moved or rebuilt. - Pro queries ``Role.objects.count()`` returns the same row counts via the new pro app state. - Reverse accessor ``product.product_member_set.all()`` resolves to Pro's Product_Member instances (the canonical owner now). - 442 tests across pro/authorization, pro/api/authorization, api_helpers, and dashboard/test_views_extended remain green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 84a68d0 commit e041bac

2 files changed

Lines changed: 121 additions & 18 deletions

File tree

dojo/authorization/models.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""
2+
Legacy backward-compat shells for the seven RBAC model classes.
3+
4+
The canonical owner of these tables is now ``pro.authorization.models``.
5+
After the paired ``SeparateDatabaseAndState`` migrations land
6+
(``dojo.0268_release_rbac_state`` + ``pro.000X_adopt_rbac_tables``),
7+
Pro's state owns the seven RBAC tables and OS's app state no longer
8+
references them.
9+
10+
The class definitions remain here as ``managed=False`` shells purely so
11+
the existing OS code that imports / isinstance-checks ``dojo.authorization
12+
.models.X`` keeps compiling. Track B step #13 simplifies the callers and
13+
deletes these shells entirely; until that lands, the shells let OS-only
14+
deployments stay functional with the legacy authorization model.
15+
"""
116
from django.db import models
217
from django.utils.translation import gettext_lazy as _
318

@@ -8,61 +23,89 @@ class Role(models.Model):
823

924
class Meta:
1025
app_label = "dojo"
26+
db_table = "dojo_role"
27+
managed = False
1128
ordering = ("name",)
1229

1330
def __str__(self):
1431
return self.name
1532

1633

1734
class Dojo_Group_Member(models.Model):
18-
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE)
19-
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE)
20-
role = models.ForeignKey(Role, on_delete=models.CASCADE, help_text=_("This role determines the permissions of the user to manage the group."), verbose_name=_("Group role"))
35+
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE, related_name="+")
36+
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE, related_name="+")
37+
role = models.ForeignKey(
38+
Role,
39+
on_delete=models.CASCADE,
40+
related_name="+",
41+
help_text=_("This role determines the permissions of the user to manage the group."),
42+
verbose_name=_("Group role"),
43+
)
2144

2245
class Meta:
2346
app_label = "dojo"
47+
db_table = "dojo_dojo_group_member"
48+
managed = False
2449

2550

2651
class Global_Role(models.Model):
27-
user = models.OneToOneField("dojo.Dojo_User", null=True, blank=True, on_delete=models.CASCADE)
28-
group = models.OneToOneField("dojo.Dojo_Group", null=True, blank=True, on_delete=models.CASCADE)
29-
role = models.ForeignKey(Role, on_delete=models.CASCADE, null=True, blank=True, help_text=_("The global role will be applied to all product types and products."), verbose_name=_("Global role"))
52+
user = models.OneToOneField("dojo.Dojo_User", null=True, blank=True, on_delete=models.CASCADE, related_name="+")
53+
group = models.OneToOneField("dojo.Dojo_Group", null=True, blank=True, on_delete=models.CASCADE, related_name="+")
54+
role = models.ForeignKey(
55+
Role,
56+
on_delete=models.CASCADE,
57+
null=True,
58+
blank=True,
59+
related_name="+",
60+
help_text=_("The global role will be applied to all product types and products."),
61+
verbose_name=_("Global role"),
62+
)
3063

3164
class Meta:
3265
app_label = "dojo"
66+
db_table = "dojo_global_role"
67+
managed = False
3368

3469

3570
class Product_Member(models.Model):
36-
product = models.ForeignKey("dojo.Product", on_delete=models.CASCADE)
37-
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE)
38-
role = models.ForeignKey(Role, on_delete=models.CASCADE)
71+
product = models.ForeignKey("dojo.Product", on_delete=models.CASCADE, related_name="+")
72+
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE, related_name="+")
73+
role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="+")
3974

4075
class Meta:
4176
app_label = "dojo"
77+
db_table = "dojo_product_member"
78+
managed = False
4279

4380

4481
class Product_Group(models.Model):
45-
product = models.ForeignKey("dojo.Product", on_delete=models.CASCADE)
46-
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE)
47-
role = models.ForeignKey(Role, on_delete=models.CASCADE)
82+
product = models.ForeignKey("dojo.Product", on_delete=models.CASCADE, related_name="+")
83+
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE, related_name="+")
84+
role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="+")
4885

4986
class Meta:
5087
app_label = "dojo"
88+
db_table = "dojo_product_group"
89+
managed = False
5190

5291

5392
class Product_Type_Member(models.Model):
54-
product_type = models.ForeignKey("dojo.Product_Type", on_delete=models.CASCADE)
55-
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE)
56-
role = models.ForeignKey(Role, on_delete=models.CASCADE)
93+
product_type = models.ForeignKey("dojo.Product_Type", on_delete=models.CASCADE, related_name="+")
94+
user = models.ForeignKey("dojo.Dojo_User", on_delete=models.CASCADE, related_name="+")
95+
role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="+")
5796

5897
class Meta:
5998
app_label = "dojo"
99+
db_table = "dojo_product_type_member"
100+
managed = False
60101

61102

62103
class Product_Type_Group(models.Model):
63-
product_type = models.ForeignKey("dojo.Product_Type", on_delete=models.CASCADE)
64-
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE)
65-
role = models.ForeignKey(Role, on_delete=models.CASCADE)
104+
product_type = models.ForeignKey("dojo.Product_Type", on_delete=models.CASCADE, related_name="+")
105+
group = models.ForeignKey("dojo.Dojo_Group", on_delete=models.CASCADE, related_name="+")
106+
role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="+")
66107

67108
class Meta:
68109
app_label = "dojo"
110+
db_table = "dojo_product_type_group"
111+
managed = False
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
Release the RBAC tables from dojo's app state.
3+
4+
Pairs with ``pro.0049_adopt_rbac_tables``: Pro adopts the seven RBAC
5+
tables (``dojo_role``, ``dojo_global_role``, ``dojo_dojo_group_member``,
6+
``dojo_product_member``, ``dojo_product_group``, ``dojo_product_type_member``,
7+
``dojo_product_type_group``) into Pro's app state via a state-only
8+
``CreateModel`` for each. This migration flips the same seven models in
9+
dojo's state to ``managed=False`` so dojo never issues DDL for them again.
10+
11+
Together this ownership transfer is bit-for-bit non-destructive: no DDL
12+
runs, no rows move, no constraints are dropped or rebuilt. The tables
13+
sit dormant in OS-only deployments (used only by Pro) and continue to
14+
hold whatever data the customer's deployment had.
15+
16+
The model class definitions in ``dojo/authorization/models.py`` remain as
17+
``managed=False`` shells until Track B step #13 simplifies the OS callers
18+
that still ``isinstance``-check / import them; at that point the shells
19+
go away entirely. Pro's ``apps.ready()`` symbol shadowing keeps the
20+
RBAC code paths active under Pro deployments regardless.
21+
"""
22+
from django.db import migrations
23+
24+
25+
class Migration(migrations.Migration):
26+
27+
dependencies = [
28+
("dojo", "0267_backfill_authorized_users"),
29+
]
30+
31+
operations = [
32+
migrations.AlterModelOptions(
33+
name="dojo_group_member",
34+
options={"managed": False},
35+
),
36+
migrations.AlterModelOptions(
37+
name="global_role",
38+
options={"managed": False},
39+
),
40+
migrations.AlterModelOptions(
41+
name="product_group",
42+
options={"managed": False},
43+
),
44+
migrations.AlterModelOptions(
45+
name="product_member",
46+
options={"managed": False},
47+
),
48+
migrations.AlterModelOptions(
49+
name="product_type_group",
50+
options={"managed": False},
51+
),
52+
migrations.AlterModelOptions(
53+
name="product_type_member",
54+
options={"managed": False},
55+
),
56+
migrations.AlterModelOptions(
57+
name="role",
58+
options={"managed": False, "ordering": ("name",)},
59+
),
60+
]

0 commit comments

Comments
 (0)