django-cfg 1.5.8__py3-none-any.whl → 1.5.14__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/parser/base.py +114 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manager for GrpcApiKey model.
|
|
3
|
+
|
|
4
|
+
Provides convenient methods for API key management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from django.contrib.auth import get_user_model
|
|
11
|
+
from django.db import models
|
|
12
|
+
from django.utils import timezone
|
|
13
|
+
|
|
14
|
+
User = get_user_model()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GrpcApiKeyManager(models.Manager):
|
|
18
|
+
"""
|
|
19
|
+
Manager for GrpcApiKey model.
|
|
20
|
+
|
|
21
|
+
Provides convenient methods for creating and validating API keys.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def create_for_user(
|
|
25
|
+
self,
|
|
26
|
+
user: User,
|
|
27
|
+
name: str,
|
|
28
|
+
description: str = "",
|
|
29
|
+
key_type: str = "service",
|
|
30
|
+
expires_in_days: Optional[int] = None,
|
|
31
|
+
created_by: Optional[User] = None,
|
|
32
|
+
) -> "GrpcApiKey":
|
|
33
|
+
"""
|
|
34
|
+
Create a new API key for a user.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
user: User this key authenticates as
|
|
38
|
+
name: Descriptive name for this key
|
|
39
|
+
description: Additional details about this key
|
|
40
|
+
key_type: Type of key (service, cli, webhook, etc.)
|
|
41
|
+
expires_in_days: Number of days until expiration (None = never)
|
|
42
|
+
created_by: User who created this key
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Created GrpcApiKey instance
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> key = GrpcApiKey.objects.create_for_user(
|
|
49
|
+
... user=admin_user,
|
|
50
|
+
... name="Analytics Service",
|
|
51
|
+
... description="Internal analytics microservice",
|
|
52
|
+
... expires_in_days=365,
|
|
53
|
+
... )
|
|
54
|
+
"""
|
|
55
|
+
expires_at = None
|
|
56
|
+
if expires_in_days:
|
|
57
|
+
expires_at = timezone.now() + timedelta(days=expires_in_days)
|
|
58
|
+
|
|
59
|
+
return self.create(
|
|
60
|
+
user=user,
|
|
61
|
+
name=name,
|
|
62
|
+
description=description,
|
|
63
|
+
key_type=key_type,
|
|
64
|
+
expires_at=expires_at,
|
|
65
|
+
created_by=created_by or user,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def get_by_key(self, key: str) -> Optional["GrpcApiKey"]:
|
|
69
|
+
"""
|
|
70
|
+
Get API key by key string.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
key: API key string
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
GrpcApiKey instance or None if not found
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> api_key = GrpcApiKey.objects.get_by_key("abc123...")
|
|
80
|
+
>>> if api_key and api_key.is_valid:
|
|
81
|
+
... user = api_key.user
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
return self.select_related("user").get(key=key)
|
|
85
|
+
except self.model.DoesNotExist:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def validate_key(self, key: str) -> Optional[User]:
|
|
89
|
+
"""
|
|
90
|
+
Validate API key and return associated user.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
key: API key string
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
User instance if key is valid, None otherwise
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> user = GrpcApiKey.objects.validate_key("abc123...")
|
|
100
|
+
>>> if user:
|
|
101
|
+
... print(f"Authenticated as {user.username}")
|
|
102
|
+
"""
|
|
103
|
+
api_key = self.get_by_key(key)
|
|
104
|
+
|
|
105
|
+
if not api_key:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
if not api_key.is_valid:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Mark as used
|
|
112
|
+
api_key.mark_used()
|
|
113
|
+
|
|
114
|
+
return api_key.user
|
|
115
|
+
|
|
116
|
+
def active(self):
|
|
117
|
+
"""
|
|
118
|
+
Get all active API keys.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
QuerySet of active keys
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> active_keys = GrpcApiKey.objects.active()
|
|
125
|
+
"""
|
|
126
|
+
return self.filter(is_active=True)
|
|
127
|
+
|
|
128
|
+
def valid(self):
|
|
129
|
+
"""
|
|
130
|
+
Get all valid API keys (active and not expired).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
QuerySet of valid keys
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> valid_keys = GrpcApiKey.objects.valid()
|
|
137
|
+
"""
|
|
138
|
+
now = timezone.now()
|
|
139
|
+
return self.filter(
|
|
140
|
+
is_active=True
|
|
141
|
+
).filter(
|
|
142
|
+
models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def for_user(self, user: User):
|
|
146
|
+
"""
|
|
147
|
+
Get all API keys for a user.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
user: User instance
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
QuerySet of keys for this user
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
>>> user_keys = GrpcApiKey.objects.for_user(request.user)
|
|
157
|
+
"""
|
|
158
|
+
return self.filter(user=user)
|
|
159
|
+
|
|
160
|
+
def expired(self):
|
|
161
|
+
"""
|
|
162
|
+
Get all expired API keys.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
QuerySet of expired keys
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> expired_keys = GrpcApiKey.objects.expired()
|
|
169
|
+
"""
|
|
170
|
+
return self.filter(
|
|
171
|
+
expires_at__isnull=False,
|
|
172
|
+
expires_at__lte=timezone.now()
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def revoke_all_for_user(self, user: User) -> int:
|
|
176
|
+
"""
|
|
177
|
+
Revoke all API keys for a user.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
user: User instance
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Number of keys revoked
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
>>> count = GrpcApiKey.objects.revoke_all_for_user(user)
|
|
187
|
+
>>> print(f"Revoked {count} keys")
|
|
188
|
+
"""
|
|
189
|
+
return self.filter(user=user, is_active=True).update(is_active=False)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
__all__ = ["GrpcApiKeyManager"]
|
|
@@ -53,6 +53,10 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
53
53
|
... )
|
|
54
54
|
>>> status.is_running
|
|
55
55
|
True
|
|
56
|
+
|
|
57
|
+
Note:
|
|
58
|
+
External/internal server detection is automatic based on env_mode.
|
|
59
|
+
Production mode assumes external server (Docker), dev/test assumes local.
|
|
56
60
|
"""
|
|
57
61
|
if pid is None:
|
|
58
62
|
pid = os.getpid()
|
|
@@ -64,18 +68,22 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
64
68
|
# Mark any existing server at this address as stopped
|
|
65
69
|
self.stop_servers_at_address(address)
|
|
66
70
|
|
|
67
|
-
# Create
|
|
68
|
-
status = self.
|
|
71
|
+
# Create or update server status (handles restart with same instance_id)
|
|
72
|
+
status, created = self.update_or_create(
|
|
69
73
|
instance_id=instance_id,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
defaults={
|
|
75
|
+
"host": host,
|
|
76
|
+
"port": port,
|
|
77
|
+
"address": address,
|
|
78
|
+
"pid": pid,
|
|
79
|
+
"hostname": hostname,
|
|
80
|
+
"status": self.model.StatusChoices.STARTING,
|
|
81
|
+
"max_workers": max_workers,
|
|
82
|
+
"enable_reflection": enable_reflection,
|
|
83
|
+
"enable_health_check": enable_health_check,
|
|
84
|
+
"started_at": timezone.now(),
|
|
85
|
+
"last_heartbeat": timezone.now(),
|
|
86
|
+
},
|
|
79
87
|
)
|
|
80
88
|
|
|
81
89
|
return status
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-11-04 05:14
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import django_cfg.apps.integrations.grpc.models.grpc_api_key
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("grpc", "0004_grpcserverstatus_registered_services"),
|
|
13
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="GrpcApiKey",
|
|
19
|
+
fields=[
|
|
20
|
+
(
|
|
21
|
+
"id",
|
|
22
|
+
models.BigAutoField(
|
|
23
|
+
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
(
|
|
27
|
+
"key",
|
|
28
|
+
models.CharField(
|
|
29
|
+
db_index=True,
|
|
30
|
+
default=django_cfg.apps.integrations.grpc.models.grpc_api_key.generate_api_key,
|
|
31
|
+
help_text="API key (auto-generated)",
|
|
32
|
+
max_length=64,
|
|
33
|
+
unique=True,
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
"name",
|
|
38
|
+
models.CharField(
|
|
39
|
+
help_text="Descriptive name for this key (e.g., 'Analytics Service')",
|
|
40
|
+
max_length=255,
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
(
|
|
44
|
+
"description",
|
|
45
|
+
models.TextField(
|
|
46
|
+
blank=True, help_text="Additional details about this key's purpose"
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
(
|
|
50
|
+
"key_type",
|
|
51
|
+
models.CharField(
|
|
52
|
+
choices=[
|
|
53
|
+
("service", "Service-to-Service"),
|
|
54
|
+
("cli", "CLI Tool"),
|
|
55
|
+
("webhook", "Webhook"),
|
|
56
|
+
("internal", "Internal System"),
|
|
57
|
+
("development", "Development"),
|
|
58
|
+
],
|
|
59
|
+
default="service",
|
|
60
|
+
help_text="Type of API key",
|
|
61
|
+
max_length=20,
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
(
|
|
65
|
+
"is_active",
|
|
66
|
+
models.BooleanField(
|
|
67
|
+
db_index=True,
|
|
68
|
+
default=True,
|
|
69
|
+
help_text="Whether this key is currently active (can be used)",
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
(
|
|
73
|
+
"expires_at",
|
|
74
|
+
models.DateTimeField(
|
|
75
|
+
blank=True,
|
|
76
|
+
db_index=True,
|
|
77
|
+
help_text="When this key expires (null = never expires)",
|
|
78
|
+
null=True,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
(
|
|
82
|
+
"last_used_at",
|
|
83
|
+
models.DateTimeField(
|
|
84
|
+
blank=True, help_text="When this key was last used", null=True
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
(
|
|
88
|
+
"request_count",
|
|
89
|
+
models.IntegerField(
|
|
90
|
+
default=0, help_text="Total number of requests made with this key"
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
"created_at",
|
|
95
|
+
models.DateTimeField(
|
|
96
|
+
auto_now_add=True, db_index=True, help_text="When this key was created"
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
(
|
|
100
|
+
"updated_at",
|
|
101
|
+
models.DateTimeField(auto_now=True, help_text="When this key was last updated"),
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
"created_by",
|
|
105
|
+
models.ForeignKey(
|
|
106
|
+
blank=True,
|
|
107
|
+
help_text="User who created this key",
|
|
108
|
+
null=True,
|
|
109
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
110
|
+
related_name="created_grpc_api_keys",
|
|
111
|
+
to=settings.AUTH_USER_MODEL,
|
|
112
|
+
),
|
|
113
|
+
),
|
|
114
|
+
(
|
|
115
|
+
"user",
|
|
116
|
+
models.ForeignKey(
|
|
117
|
+
help_text="User this key authenticates as",
|
|
118
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
119
|
+
related_name="grpc_api_keys",
|
|
120
|
+
to=settings.AUTH_USER_MODEL,
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
],
|
|
124
|
+
options={
|
|
125
|
+
"verbose_name": "gRPC API Key",
|
|
126
|
+
"verbose_name_plural": "gRPC API Keys",
|
|
127
|
+
"db_table": "django_cfg_grpc_api_key",
|
|
128
|
+
"ordering": ["-created_at"],
|
|
129
|
+
"indexes": [
|
|
130
|
+
models.Index(
|
|
131
|
+
fields=["user", "-created_at"], name="django_cfg__user_id_9c5276_idx"
|
|
132
|
+
),
|
|
133
|
+
models.Index(
|
|
134
|
+
fields=["is_active", "-created_at"], name="django_cfg__is_acti_26421c_idx"
|
|
135
|
+
),
|
|
136
|
+
models.Index(fields=["expires_at"], name="django_cfg__expires_8f83cf_idx"),
|
|
137
|
+
models.Index(
|
|
138
|
+
fields=["key_type", "-created_at"], name="django_cfg__key_typ_6ad1cb_idx"
|
|
139
|
+
),
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
),
|
|
143
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-11-04 05:30
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
("grpc", "0005_grpcapikey"),
|
|
12
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name="grpcrequestlog",
|
|
18
|
+
name="api_key",
|
|
19
|
+
field=models.ForeignKey(
|
|
20
|
+
blank=True,
|
|
21
|
+
help_text="API key used for authentication (if applicable)",
|
|
22
|
+
null=True,
|
|
23
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
24
|
+
related_name="request_logs",
|
|
25
|
+
to="grpc.grpcapikey",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
migrations.AddIndex(
|
|
29
|
+
model_name="grpcrequestlog",
|
|
30
|
+
index=models.Index(
|
|
31
|
+
fields=["api_key", "-created_at"], name="django_cfg__api_key_218ecc_idx"
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC API Key Model.
|
|
3
|
+
|
|
4
|
+
Django model for managing API keys used for gRPC authentication.
|
|
5
|
+
Provides secure, revocable authentication for services and CLI tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import secrets
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
from django.db import models
|
|
11
|
+
from django.utils import timezone
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_api_key() -> str:
|
|
15
|
+
"""
|
|
16
|
+
Generate a secure random API key.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
32-character hex string (128 bits of entropy)
|
|
20
|
+
"""
|
|
21
|
+
return secrets.token_hex(32)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GrpcApiKey(models.Model):
|
|
25
|
+
"""
|
|
26
|
+
API Key for gRPC authentication.
|
|
27
|
+
|
|
28
|
+
Provides secure, revocable authentication for:
|
|
29
|
+
- Service-to-service communication
|
|
30
|
+
- CLI tools and scripts
|
|
31
|
+
- Internal systems
|
|
32
|
+
- Development and testing
|
|
33
|
+
|
|
34
|
+
Features:
|
|
35
|
+
- Auto-generated secure keys
|
|
36
|
+
- User association for permissions
|
|
37
|
+
- Expiration support
|
|
38
|
+
- Usage tracking
|
|
39
|
+
- Easy revocation via admin
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> # Create API key for a service
|
|
43
|
+
>>> key = GrpcApiKey.objects.create_for_user(
|
|
44
|
+
... user=admin_user,
|
|
45
|
+
... name="Analytics Service",
|
|
46
|
+
... description="Internal analytics microservice"
|
|
47
|
+
... )
|
|
48
|
+
>>> print(key.key) # Use this in service config
|
|
49
|
+
|
|
50
|
+
>>> # Check if key is valid
|
|
51
|
+
>>> if key.is_valid:
|
|
52
|
+
... user = key.user
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Custom manager
|
|
56
|
+
from ..managers.grpc_api_key import GrpcApiKeyManager
|
|
57
|
+
objects: GrpcApiKeyManager = GrpcApiKeyManager()
|
|
58
|
+
|
|
59
|
+
class KeyTypeChoices(models.TextChoices):
|
|
60
|
+
"""Type of API key."""
|
|
61
|
+
SERVICE = "service", "Service-to-Service"
|
|
62
|
+
CLI = "cli", "CLI Tool"
|
|
63
|
+
WEBHOOK = "webhook", "Webhook"
|
|
64
|
+
INTERNAL = "internal", "Internal System"
|
|
65
|
+
DEVELOPMENT = "development", "Development"
|
|
66
|
+
|
|
67
|
+
# Identity
|
|
68
|
+
key = models.CharField(
|
|
69
|
+
max_length=64,
|
|
70
|
+
unique=True,
|
|
71
|
+
default=generate_api_key,
|
|
72
|
+
db_index=True,
|
|
73
|
+
help_text="API key (auto-generated)",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
name = models.CharField(
|
|
77
|
+
max_length=255,
|
|
78
|
+
help_text="Descriptive name for this key (e.g., 'Analytics Service')",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
description = models.TextField(
|
|
82
|
+
blank=True,
|
|
83
|
+
help_text="Additional details about this key's purpose",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
key_type = models.CharField(
|
|
87
|
+
max_length=20,
|
|
88
|
+
choices=KeyTypeChoices.choices,
|
|
89
|
+
default=KeyTypeChoices.SERVICE,
|
|
90
|
+
help_text="Type of API key",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# User association
|
|
94
|
+
user = models.ForeignKey(
|
|
95
|
+
settings.AUTH_USER_MODEL,
|
|
96
|
+
on_delete=models.CASCADE,
|
|
97
|
+
related_name="grpc_api_keys",
|
|
98
|
+
help_text="User this key authenticates as",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Status
|
|
102
|
+
is_active = models.BooleanField(
|
|
103
|
+
default=True,
|
|
104
|
+
db_index=True,
|
|
105
|
+
help_text="Whether this key is currently active (can be used)",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Expiration
|
|
109
|
+
expires_at = models.DateTimeField(
|
|
110
|
+
null=True,
|
|
111
|
+
blank=True,
|
|
112
|
+
db_index=True,
|
|
113
|
+
help_text="When this key expires (null = never expires)",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Usage tracking
|
|
117
|
+
last_used_at = models.DateTimeField(
|
|
118
|
+
null=True,
|
|
119
|
+
blank=True,
|
|
120
|
+
help_text="When this key was last used",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
request_count = models.IntegerField(
|
|
124
|
+
default=0,
|
|
125
|
+
help_text="Total number of requests made with this key",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Metadata
|
|
129
|
+
created_by = models.ForeignKey(
|
|
130
|
+
settings.AUTH_USER_MODEL,
|
|
131
|
+
on_delete=models.SET_NULL,
|
|
132
|
+
null=True,
|
|
133
|
+
blank=True,
|
|
134
|
+
related_name="created_grpc_api_keys",
|
|
135
|
+
help_text="User who created this key",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Timestamps
|
|
139
|
+
created_at = models.DateTimeField(
|
|
140
|
+
auto_now_add=True,
|
|
141
|
+
db_index=True,
|
|
142
|
+
help_text="When this key was created",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
updated_at = models.DateTimeField(
|
|
146
|
+
auto_now=True,
|
|
147
|
+
help_text="When this key was last updated",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
class Meta:
|
|
151
|
+
db_table = "django_cfg_grpc_api_key"
|
|
152
|
+
ordering = ["-created_at"]
|
|
153
|
+
indexes = [
|
|
154
|
+
models.Index(fields=["user", "-created_at"]),
|
|
155
|
+
models.Index(fields=["is_active", "-created_at"]),
|
|
156
|
+
models.Index(fields=["expires_at"]),
|
|
157
|
+
models.Index(fields=["key_type", "-created_at"]),
|
|
158
|
+
]
|
|
159
|
+
verbose_name = "gRPC API Key"
|
|
160
|
+
verbose_name_plural = "gRPC API Keys"
|
|
161
|
+
|
|
162
|
+
def __str__(self) -> str:
|
|
163
|
+
"""String representation."""
|
|
164
|
+
status = "✓" if self.is_valid else "✗"
|
|
165
|
+
return f"{status} {self.name} ({self.user.username})"
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def is_expired(self) -> bool:
|
|
169
|
+
"""Check if key has expired."""
|
|
170
|
+
if not self.expires_at:
|
|
171
|
+
return False
|
|
172
|
+
return timezone.now() > self.expires_at
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def is_valid(self) -> bool:
|
|
176
|
+
"""Check if key is valid (active and not expired)."""
|
|
177
|
+
return self.is_active and not self.is_expired
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def masked_key(self) -> str:
|
|
181
|
+
"""Return masked version of key for display."""
|
|
182
|
+
if len(self.key) <= 8:
|
|
183
|
+
return self.key
|
|
184
|
+
return f"{self.key[:4]}...{self.key[-4:]}"
|
|
185
|
+
|
|
186
|
+
def mark_used(self) -> None:
|
|
187
|
+
"""Mark this key as used (update last_used_at and increment counter)."""
|
|
188
|
+
self.last_used_at = timezone.now()
|
|
189
|
+
self.request_count += 1
|
|
190
|
+
self.save(update_fields=["last_used_at", "request_count"])
|
|
191
|
+
|
|
192
|
+
def revoke(self) -> None:
|
|
193
|
+
"""Revoke this key (set is_active=False)."""
|
|
194
|
+
self.is_active = False
|
|
195
|
+
self.save(update_fields=["is_active"])
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
__all__ = ["GrpcApiKey", "generate_api_key"]
|
|
@@ -9,6 +9,7 @@ from django.conf import settings
|
|
|
9
9
|
from django.db import models
|
|
10
10
|
from django.utils import timezone
|
|
11
11
|
|
|
12
|
+
from .grpc_api_key import GrpcApiKey
|
|
12
13
|
|
|
13
14
|
class GRPCRequestLog(models.Model):
|
|
14
15
|
"""
|
|
@@ -138,6 +139,15 @@ class GRPCRequestLog(models.Model):
|
|
|
138
139
|
help_text="Authenticated user (if applicable)",
|
|
139
140
|
)
|
|
140
141
|
|
|
142
|
+
api_key = models.ForeignKey(
|
|
143
|
+
GrpcApiKey,
|
|
144
|
+
on_delete=models.SET_NULL,
|
|
145
|
+
null=True,
|
|
146
|
+
blank=True,
|
|
147
|
+
related_name="request_logs",
|
|
148
|
+
help_text="API key used for authentication (if applicable)",
|
|
149
|
+
)
|
|
150
|
+
|
|
141
151
|
is_authenticated = models.BooleanField(
|
|
142
152
|
default=False,
|
|
143
153
|
db_index=True,
|
|
@@ -186,6 +196,7 @@ class GRPCRequestLog(models.Model):
|
|
|
186
196
|
models.Index(fields=["method_name", "-created_at"]),
|
|
187
197
|
models.Index(fields=["status", "-created_at"]),
|
|
188
198
|
models.Index(fields=["user", "-created_at"]),
|
|
199
|
+
models.Index(fields=["api_key", "-created_at"]),
|
|
189
200
|
models.Index(fields=["grpc_status_code", "-created_at"]),
|
|
190
201
|
]
|
|
191
202
|
verbose_name = "gRPC Request Log"
|