feat: add enterprise identity and channel integrations

This commit is contained in:
hailin
2026-07-27 19:03:42 +08:00
parent d93cb75c10
commit e69368a2f3
44 changed files with 5045 additions and 69 deletions

View File

@@ -0,0 +1,117 @@
# Generated by Django 5.2.15 on 2026-07-27 08:41
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_reorganize_navigation_menus'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='EnterpriseIdentityProvider',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=80, unique=True)),
('name', models.CharField(max_length=120)),
('provider_type', models.CharField(choices=[('oidc', 'OpenID Connect'), ('ldap', 'LDAP')], default='oidc', max_length=20)),
('enabled', models.BooleanField(default=False)),
('button_label', models.CharField(blank=True, max_length=120)),
('issuer', models.URLField(blank=True, max_length=500)),
('metadata_url', models.URLField(blank=True, max_length=500)),
('client_id', models.CharField(blank=True, max_length=255)),
('credential_encrypted', models.TextField(blank=True)),
('scopes', models.JSONField(blank=True, default=list)),
('claim_mappings', models.JSONField(blank=True, default=dict)),
('configuration', models.JSONField(blank=True, default=dict)),
('auto_create_users', models.BooleanField(default=True)),
('sync_profile', models.BooleanField(default=True)),
('sync_groups', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_identity_providers', to=settings.AUTH_USER_MODEL)),
('updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_identity_providers', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['name', 'id'],
},
),
migrations.CreateModel(
name='EnterpriseGroupMapping',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('external_group', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enterprise_identity_mappings', to='accounts.usergroup')),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_mappings', to='accounts.enterpriseidentityprovider')),
],
options={
'ordering': ['provider_id', 'external_group'],
},
),
migrations.CreateModel(
name='EnterpriseDirectorySyncRun',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('running', '运行中'), ('completed', '已完成'), ('partial', '部分完成'), ('failed', '失败')], default='running', max_length=20)),
('total_count', models.PositiveIntegerField(default=0)),
('created_count', models.PositiveIntegerField(default=0)),
('updated_count', models.PositiveIntegerField(default=0)),
('failed_count', models.PositiveIntegerField(default=0)),
('errors', models.JSONField(blank=True, default=list)),
('started_at', models.DateTimeField(auto_now_add=True)),
('finished_at', models.DateTimeField(blank=True, null=True)),
('initiated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='enterprise_directory_sync_runs', to=settings.AUTH_USER_MODEL)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='directory_sync_runs', to='accounts.enterpriseidentityprovider')),
],
options={
'ordering': ['-started_at', '-id'],
},
),
migrations.CreateModel(
name='ExternalIdentity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=255)),
('claims_snapshot', models.JSONField(blank=True, default=dict)),
('last_login_at', models.DateTimeField(blank=True, null=True)),
('last_synced_at', models.DateTimeField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='external_identities', to='accounts.enterpriseidentityprovider')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='external_identities', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['provider_id', 'subject'],
},
),
migrations.AddIndex(
model_name='enterpriseidentityprovider',
index=models.Index(fields=['provider_type', 'enabled'], name='acct_idp_type_enabled_idx'),
),
migrations.AddConstraint(
model_name='enterprisegroupmapping',
constraint=models.UniqueConstraint(fields=('provider', 'external_group'), name='acct_idp_external_group_uniq'),
),
migrations.AddIndex(
model_name='enterprisedirectorysyncrun',
index=models.Index(fields=['provider', 'started_at'], name='acct_dirsync_idp_started_idx'),
),
migrations.AddIndex(
model_name='externalidentity',
index=models.Index(fields=['user', 'provider'], name='acct_identity_user_idp_idx'),
),
migrations.AddConstraint(
model_name='externalidentity',
constraint=models.UniqueConstraint(fields=('provider', 'subject'), name='acct_identity_provider_subject_uniq'),
),
migrations.AddConstraint(
model_name='externalidentity',
constraint=models.UniqueConstraint(fields=('provider', 'user'), name='acct_identity_provider_user_uniq'),
),
]

View File

@@ -0,0 +1,36 @@
from django.db import migrations
def add_identity_menu(apps, schema_editor):
menu_item = apps.get_model("accounts", "MenuItem")
parent = menu_item.objects.filter(code="system").first()
menu_item.objects.update_or_create(
code="system.identity",
defaults={
"title": "企业身份",
"path": "#/system/identity",
"icon": "shield",
"parent": parent,
"sort_order": 50,
"permission_code": "system.setting.manage",
"is_visible": True,
"is_enabled": True,
},
)
menu_item.objects.filter(code="system.audit").update(sort_order=60)
menu_item.objects.filter(code="system.design").update(sort_order=70)
def remove_identity_menu(apps, schema_editor):
menu_item = apps.get_model("accounts", "MenuItem")
menu_item.objects.filter(code="system.identity").delete()
menu_item.objects.filter(code="system.audit").update(sort_order=50)
menu_item.objects.filter(code="system.design").update(sort_order=60)
class Migration(migrations.Migration):
dependencies = [("accounts", "0003_enterpriseidentityprovider_enterprisegroupmapping_and_more")]
operations = [
migrations.RunPython(add_identity_menu, remove_identity_menu),
]