Ajout type contrat

This commit is contained in:
2026-04-29 11:52:03 +02:00
parent 375549cb30
commit 1c0e4c3048
10530 changed files with 1842149 additions and 158 deletions

View File

@@ -0,0 +1 @@
default_app_config = 'simple_sso.sso_server.apps.SimpleSSOServer'

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class SimpleSSOServer(AppConfig):
name = 'simple_sso.sso_server'

View File

@@ -0,0 +1,35 @@
from django.db import migrations, models
from django.utils import timezone
from django.conf import settings
import simple_sso.sso_server.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Consumer',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=100)),
('private_key', models.CharField(default=simple_sso.sso_server.models.ConsumerSecretKeyGenerator('private_key'), unique=True, max_length=64)),
('public_key', models.CharField(default=simple_sso.sso_server.models.ConsumerSecretKeyGenerator('public_key'), unique=True, max_length=64)),
],
),
migrations.CreateModel(
name='Token',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('request_token', models.CharField(default=simple_sso.sso_server.models.TokenSecretKeyGenerator('request_token'), unique=True, max_length=64)),
('access_token', models.CharField(default=simple_sso.sso_server.models.TokenSecretKeyGenerator('access_token'), unique=True, max_length=64)),
('timestamp', models.DateTimeField(default=timezone.now)),
('redirect_to', models.CharField(max_length=255)),
('consumer', models.ForeignKey(related_name='tokens', to='sso_server.Consumer', on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)),
],
),
]

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sso_server', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='consumer',
name='name',
field=models.CharField(unique=True, max_length=255),
),
]

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sso_server', '0002_consumer_name_max_length'),
]
operations = [
migrations.AlterField(
model_name='token',
name='redirect_to',
field=models.CharField(max_length=1023),
),
]

View File

@@ -0,0 +1,79 @@
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.deconstruct import deconstructible
from ..utils import gen_secret_key
@deconstructible
class SecretKeyGenerator:
"""
Helper to give default values to Client.secret and Client.key
"""
def __init__(self, field):
self.field = field
def __call__(self):
key = gen_secret_key(64)
while self.get_model().objects.filter(**{self.field: key}).exists():
key = gen_secret_key(64)
return key
class ConsumerSecretKeyGenerator(SecretKeyGenerator):
def get_model(self):
return Consumer
class TokenSecretKeyGenerator(SecretKeyGenerator):
def get_model(self):
return Token
class Consumer(models.Model):
name = models.CharField(max_length=255, unique=True)
private_key = models.CharField(
max_length=64, unique=True,
default=ConsumerSecretKeyGenerator('private_key')
)
public_key = models.CharField(
max_length=64, unique=True,
default=ConsumerSecretKeyGenerator('public_key')
)
def __unicode__(self):
return self.name
def rotate_keys(self):
self.secret = ConsumerSecretKeyGenerator('private_key')()
self.key = ConsumerSecretKeyGenerator('public_key')()
self.save()
class Token(models.Model):
consumer = models.ForeignKey(
Consumer,
related_name='tokens',
on_delete=models.CASCADE,
)
request_token = models.CharField(
unique=True, max_length=64,
default=TokenSecretKeyGenerator('request_token')
)
access_token = models.CharField(
unique=True, max_length=64,
default=TokenSecretKeyGenerator('access_token')
)
timestamp = models.DateTimeField(default=timezone.now)
redirect_to = models.CharField(max_length=1023)
user = models.ForeignKey(
getattr(settings, 'AUTH_USER_MODEL', 'auth.User'),
null=True,
on_delete=models.CASCADE,
)
def refresh(self):
self.timestamp = timezone.now()
self.save()

View File

@@ -0,0 +1,175 @@
import datetime
from urllib.parse import urlparse, urlencode, urlunparse
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from django.http import (HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, QueryDict)
from django.urls import re_path
from django.urls import reverse
from django.utils import timezone
from django.views.generic.base import View
from itsdangerous import URLSafeTimedSerializer
from simple_sso.sso_server.models import Token, Consumer
from simple_sso.utils import BaseProvider, provider_wrapper
class Provider(BaseProvider):
max_age = 5
def __init__(self, server):
self.server = server
def get_private_key(self, public_key):
try:
self.consumer = Consumer.objects.get(public_key=public_key)
except Consumer.DoesNotExist:
return None
return self.consumer.private_key
class RequestTokenProvider(Provider):
def provide(self, data):
redirect_to = data['redirect_to']
token = Token.objects.create(consumer=self.consumer, redirect_to=redirect_to)
return {'request_token': token.request_token}
class AuthorizeView(View):
"""
The client get's redirected to this view with the `request_token` obtained
by the Request Token Request by the client application beforehand.
This view checks if the user is logged in on the server application and if
that user has the necessary rights.
If the user is not logged in, the user is prompted to log in.
"""
server = None
def get(self, request):
request_token = request.GET.get('token', None)
if not request_token:
return self.missing_token_argument()
try:
self.token = Token.objects.select_related('consumer').get(request_token=request_token)
except Token.DoesNotExist:
return self.token_not_found()
if not self.check_token_timeout():
return self.token_timeout()
self.token.refresh()
if request.user.is_authenticated:
return self.handle_authenticated_user()
else:
return self.handle_unauthenticated_user()
def missing_token_argument(self):
return HttpResponseBadRequest('Token missing')
def token_not_found(self):
return HttpResponseForbidden('Token not found')
def token_timeout(self):
return HttpResponseForbidden('Token timed out')
def check_token_timeout(self):
delta = timezone.now() - self.token.timestamp
if delta > self.server.token_timeout:
self.token.delete()
return False
else:
return True
def handle_authenticated_user(self):
if self.server.has_access(self.request.user, self.token.consumer):
return self.success()
else:
return self.access_denied()
def handle_unauthenticated_user(self):
next_ = '%s?%s' % (self.request.path, urlencode([('token', self.token.request_token)]))
url = '%s?%s' % (reverse(self.server.auth_view_name), urlencode([('next', next_)]))
return HttpResponseRedirect(url)
def access_denied(self):
return HttpResponseForbidden("Access denied")
def success(self):
self.token.user = self.request.user
self.token.save()
serializer = URLSafeTimedSerializer(self.token.consumer.private_key)
parse_result = urlparse(self.token.redirect_to)
query_dict = QueryDict(parse_result.query, mutable=True)
query_dict['access_token'] = serializer.dumps(self.token.access_token)
url = urlunparse((parse_result.scheme, parse_result.netloc, parse_result.path, '', query_dict.urlencode(), ''))
return HttpResponseRedirect(url)
class VerificationProvider(Provider, AuthorizeView):
def provide(self, data):
token = data['access_token']
try:
self.token = Token.objects.select_related('user').get(access_token=token, consumer=self.consumer)
except Token.DoesNotExist:
return self.token_not_found()
if not self.check_token_timeout():
return self.token_timeout()
if not self.token.user:
return self.token_not_bound()
extra_data = data.get('extra_data', None)
return self.server.get_user_data(
self.token.user, self.consumer, extra_data=extra_data)
def token_not_bound(self):
return HttpResponseForbidden("Invalid token")
class ConsumerAdmin(ModelAdmin):
readonly_fields = ['public_key', 'private_key']
class Server:
request_token_provider = RequestTokenProvider
authorize_view = AuthorizeView
verification_provider = VerificationProvider
token_timeout = datetime.timedelta(minutes=5)
client_admin = ConsumerAdmin
auth_view_name = 'login'
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
self.register_admin()
def register_admin(self):
admin.site.register(Consumer, self.client_admin)
def has_access(self, user, consumer):
return True
def get_user_extra_data(self, user, consumer, extra_data):
raise NotImplementedError()
def get_user_data(self, user, consumer, extra_data=None):
user_data = {
'username': user.username,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'is_staff': False,
'is_superuser': False,
'is_active': user.is_active,
}
if extra_data:
user_data['extra_data'] = self.get_user_extra_data(
user, consumer, extra_data)
return user_data
def get_urls(self):
return [
re_path(r'^request-token/$', provider_wrapper(self.request_token_provider(server=self)),
name='simple-sso-request-token'),
re_path(r'^authorize/$', self.authorize_view.as_view(server=self), name='simple-sso-authorize'),
re_path(r'^verify/$', provider_wrapper(
self.verification_provider(server=self)), name='simple-sso-verify'),
]