feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run

This commit is contained in:
CodexPi
2026-11-14 20:23:59 +01:00
commit 995b17a6e2
295 changed files with 36803 additions and 0 deletions

View File

@@ -0,0 +1 @@
"""Application permissions attached to the existing Django identity."""

View File

@@ -0,0 +1,37 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
APPLICATIONS = (
"auto-care", "autonomous-dev-studio", "codex-commander", "knowledge-graph",
"loutre-brain", "loutre-finance", "loutre-home", "loutre-os",
"nasflix-ai-media-curator", "nasflix-media-health", "network-guardian",
"pi-doctor", "print-pilot", "pwa-factory", "reverse-proxy-manager",
"smart-inventory", "trip-organizer",
)
ROLES = ("reader", "operator", "admin")
class Migration(migrations.Migration):
initial = True
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
operations = [
migrations.CreateModel(
name="ApplicationGrant",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("application", models.CharField(max_length=64, choices=[(name, name) for name in APPLICATIONS])),
("role", models.CharField(max_length=16, choices=[(name, name) for name in ROLES])),
("updated_at", models.DateTimeField(auto_now=True)),
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
"constraints": [
models.UniqueConstraint(fields=("user", "application"), name="identity_user_application"),
models.CheckConstraint(condition=models.Q(application__in=APPLICATIONS), name="identity_known_application"),
models.CheckConstraint(condition=models.Q(role__in=ROLES), name="identity_known_role"),
]
},
)
]

View File

@@ -0,0 +1,25 @@
from django.conf import settings
from django.db import models
APPLICATIONS = (
"auto-care", "autonomous-dev-studio", "codex-commander", "knowledge-graph",
"loutre-brain", "loutre-finance", "loutre-home", "loutre-os",
"nasflix-ai-media-curator", "nasflix-media-health", "network-guardian",
"pi-doctor", "print-pilot", "pwa-factory", "reverse-proxy-manager",
"smart-inventory", "trip-organizer",
)
ROLES = ("reader", "operator", "admin")
class ApplicationGrant(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
application = models.CharField(max_length=64, choices=[(name, name) for name in APPLICATIONS])
role = models.CharField(max_length=16, choices=[(name, name) for name in ROLES])
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=("user", "application"), name="identity_user_application"),
models.CheckConstraint(condition=models.Q(application__in=APPLICATIONS), name="identity_known_application"),
models.CheckConstraint(condition=models.Q(role__in=ROLES), name="identity_known_role"),
]

104
django_ui/identity/views.py Normal file
View File

@@ -0,0 +1,104 @@
"""Same-origin identity contract in the existing Django service, deny by default."""
from functools import wraps
from django.contrib.auth import get_user_model
from django.db import DatabaseError
from django.http import JsonResponse
from django.views.decorators.http import require_GET, require_POST, require_http_methods
from django_ui.views import AUDIT, client_ip, json_body, security_headers
from .models import APPLICATIONS, ROLES, ApplicationGrant
PERMISSIONS = {"reader": ("read",), "operator": ("read", "write"), "admin": ("read", "write", "admin")}
def reply(body, status=200):
response = security_headers(JsonResponse(body, status=status))
response["Vary"] = "Cookie"
return response
def identity_request(view):
@wraps(view)
def guarded(request, application, **kwargs):
try:
if not request.user.is_authenticated:
return reply({"detail": "Authentification requise"}, 401)
if application not in APPLICATIONS:
return reply({"detail": "Application inconnue"}, 404)
if request.headers.get("Sec-Fetch-Site") == "cross-site":
return reply({"detail": "Requête intersite refusée"}, 403)
if request.method not in {"GET", "HEAD", "OPTIONS"} and (
request.content_type != "application/json" or len(request.body) > 4096
):
return reply({"detail": "Corps JSON limité requis"}, 400)
return view(request, application, **kwargs)
except DatabaseError:
return reply({"detail": "Identité indisponible ; accès refusé"}, 503)
return guarded
def principal(request, application):
grant = ApplicationGrant.objects.filter(user=request.user, application=application).first()
if not grant or grant.role not in PERMISSIONS:
return None
return {
"issuer": "django-codexpi",
"subject": str(request.user.pk),
"username": request.user.get_username(),
"application": application,
"role": grant.role,
"permissions": list(PERMISSIONS[grant.role]),
}
@require_GET
@identity_request
def application_identity(request, application):
identity = principal(request, application)
return reply(identity) if identity else reply({"detail": "Accès à cette application refusé"}, 403)
@require_POST
@identity_request
def authorize(request, application):
body = json_body(request)
if set(body) != {"permission"} or body["permission"] not in ("read", "write", "admin"):
return reply({"detail": "Permission invalide"}, 400)
identity = principal(request, application)
if not identity or body["permission"] not in identity["permissions"]:
return reply({"detail": "Permission refusée"}, 403)
return reply({**identity, "authorized_permission": body["permission"]})
@require_http_methods(["GET", "PUT", "DELETE"])
@identity_request
def application_grant(request, application, user_id):
if not (request.user.is_staff or request.user.is_superuser):
return reply({"detail": "Administration des identités requise"}, 403)
target = get_user_model().objects.filter(pk=user_id).first()
if not target:
return reply({"detail": "Compte introuvable"}, 404)
grants = ApplicationGrant.objects.filter(user=target, application=application)
if request.method == "GET":
grant = grants.first()
return reply({"subject": str(target.pk), "application": application, "role": grant.role if grant else None})
if request.method == "DELETE":
grants.delete()
role = None
else:
body = json_body(request)
if set(body) != {"role"} or body["role"] not in ROLES:
return reply({"detail": "Rôle invalide"}, 400)
role = body["role"]
ApplicationGrant.objects.update_or_create(user=target, application=application, defaults={"role": role})
AUDIT.log(
"identity.grant_revoked" if role is None else "identity.grant_updated",
client_ip(request),
username=request.user.get_username(),
project_id=application,
outcome="allowed",
reason=f"target_user={target.pk};role={role or 'none'}",
)
return reply({"subject": str(target.pk), "application": application, "role": role})