from django.conf import settings from django.db import models class UserGroup(models.Model): name = models.CharField(max_length=120, unique=True) code = models.CharField(max_length=80, unique=True) description = models.TextField(blank=True) users = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="business_groups" ) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["name"] def __str__(self) -> str: return self.name class MenuItem(models.Model): code = models.CharField(max_length=120, unique=True) title = models.CharField(max_length=120) path = models.CharField(max_length=200, blank=True) icon = models.CharField(max_length=80, blank=True) parent = models.ForeignKey( "self", null=True, blank=True, on_delete=models.CASCADE, related_name="children", ) sort_order = models.PositiveIntegerField(default=0) permission_code = models.CharField(max_length=120, blank=True) is_visible = models.BooleanField(default=True) is_enabled = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["sort_order", "id"] def __str__(self) -> str: return self.title class EnterpriseIdentityProvider(models.Model): class ProviderType(models.TextChoices): OIDC = "oidc", "OpenID Connect" LDAP = "ldap", "LDAP" slug = models.SlugField(max_length=80, unique=True) name = models.CharField(max_length=120) provider_type = models.CharField( max_length=20, choices=ProviderType.choices, default=ProviderType.OIDC, ) enabled = models.BooleanField(default=False) button_label = models.CharField(max_length=120, blank=True) issuer = models.URLField(max_length=500, blank=True) metadata_url = models.URLField(max_length=500, blank=True) client_id = models.CharField(max_length=255, blank=True) credential_encrypted = models.TextField(blank=True) scopes = models.JSONField(default=list, blank=True) claim_mappings = models.JSONField(default=dict, blank=True) configuration = models.JSONField(default=dict, blank=True) auto_create_users = models.BooleanField(default=True) sync_profile = models.BooleanField(default=True) sync_groups = models.BooleanField(default=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_identity_providers", ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="updated_identity_providers", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["name", "id"] indexes = [ models.Index(fields=["provider_type", "enabled"], name="acct_idp_type_enabled_idx"), ] def __str__(self) -> str: return self.name class ExternalIdentity(models.Model): provider = models.ForeignKey( EnterpriseIdentityProvider, on_delete=models.CASCADE, related_name="external_identities", ) subject = models.CharField(max_length=255) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="external_identities", ) claims_snapshot = models.JSONField(default=dict, blank=True) last_login_at = models.DateTimeField(null=True, blank=True) last_synced_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["provider_id", "subject"] constraints = [ models.UniqueConstraint( fields=["provider", "subject"], name="acct_identity_provider_subject_uniq", ), models.UniqueConstraint( fields=["provider", "user"], name="acct_identity_provider_user_uniq", ), ] indexes = [ models.Index(fields=["user", "provider"], name="acct_identity_user_idp_idx"), ] class EnterpriseGroupMapping(models.Model): provider = models.ForeignKey( EnterpriseIdentityProvider, on_delete=models.CASCADE, related_name="group_mappings", ) external_group = models.CharField(max_length=255) user_group = models.ForeignKey( UserGroup, on_delete=models.CASCADE, related_name="enterprise_identity_mappings", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["provider_id", "external_group"] constraints = [ models.UniqueConstraint( fields=["provider", "external_group"], name="acct_idp_external_group_uniq", ), ] class EnterpriseDirectorySyncRun(models.Model): class Status(models.TextChoices): RUNNING = "running", "运行中" COMPLETED = "completed", "已完成" PARTIAL = "partial", "部分完成" FAILED = "failed", "失败" provider = models.ForeignKey( EnterpriseIdentityProvider, on_delete=models.CASCADE, related_name="directory_sync_runs", ) status = models.CharField(max_length=20, choices=Status.choices, default=Status.RUNNING) 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(default=list, blank=True) initiated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="enterprise_directory_sync_runs", ) started_at = models.DateTimeField(auto_now_add=True) finished_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ["-started_at", "-id"] indexes = [ models.Index(fields=["provider", "started_at"], name="acct_dirsync_idp_started_idx"), ]