mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-10 02:39:41 +00:00
3fd5a4404c
We need to integrate with external applications. Objective: enable them to securely generate room links with proper ownership attribution. Proposed solution: Following the OAuth2 Machine-to-Machine specification, we expose an endpoint allowing external applications to exchange a client_id and client_secret pair for a JWT. This JWT is valid only within a well-scoped, isolated external API, served through a dedicated viewset. This commit introduces a model to persist application records in the database. The main challenge lies in generating a secure client_secret and ensuring it is properly stored. The restframework-apikey dependency was discarded, as its approach diverges significantly from OAuth2. Instead, inspiration was taken from oauthlib and django-oauth-toolkit. However, their implementations proved either too heavy or not entirely suitable for the intended use case. To avoid pulling in large dependencies for minimal utility, the necessary components were selectively copied, adapted, and improved. A generic SecretField was introduced, designed for reuse and potentially suitable for upstream contribution to Django. Secrets are exposed only once at object creation time in the Django admin. Once the object is saved, the secret is immediately hashed, ensuring it can never be retrieved again. One limitation remains: enforcing client_id and client_secret as read-only during edits. At object creation, marking them read-only excluded them from the Django form, which unintentionally regenerated new values. This area requires further refinement. The design prioritizes configurability while adhering to the principle of least privilege. By default, new applications are created without any assigned scopes, preventing them from performing actions on the API until explicitly configured. If no domain is specified, domain delegation is not applied, allowing tokens to be issued for any email domain.
156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
"""
|
|
Core application factories
|
|
"""
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.hashers import make_password
|
|
from django.utils.text import slugify
|
|
|
|
import factory.fuzzy
|
|
from faker import Faker
|
|
|
|
from core import models, utils
|
|
|
|
fake = Faker()
|
|
|
|
|
|
class UserFactory(factory.django.DjangoModelFactory):
|
|
"""A factory to random users for testing purposes."""
|
|
|
|
class Meta:
|
|
model = models.User
|
|
|
|
sub = factory.Sequence(lambda n: f"user{n!s}")
|
|
email = factory.Faker("email")
|
|
full_name = factory.Faker("name")
|
|
short_name = factory.Faker("first_name")
|
|
language = factory.fuzzy.FuzzyChoice([lang[0] for lang in settings.LANGUAGES])
|
|
password = make_password("password")
|
|
|
|
|
|
class ResourceFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake resources for testing."""
|
|
|
|
class Meta:
|
|
model = models.Resource
|
|
skip_postgeneration_save = True
|
|
|
|
@factory.post_generation
|
|
def users(self, create, extracted, **kwargs):
|
|
"""Add users to resource from a given list of users."""
|
|
if create and extracted:
|
|
for item in extracted:
|
|
if isinstance(item, models.User):
|
|
UserResourceAccessFactory(resource=self, user=item)
|
|
else:
|
|
UserResourceAccessFactory(resource=self, user=item[0], role=item[1])
|
|
|
|
self.save()
|
|
|
|
|
|
class UserResourceAccessFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake resource user accesses for testing."""
|
|
|
|
class Meta:
|
|
model = models.ResourceAccess
|
|
|
|
resource = factory.SubFactory(ResourceFactory)
|
|
user = factory.SubFactory(UserFactory)
|
|
role = factory.fuzzy.FuzzyChoice(models.RoleChoices.values)
|
|
|
|
|
|
class RoomFactory(ResourceFactory):
|
|
"""Create fake rooms for testing."""
|
|
|
|
class Meta:
|
|
model = models.Room
|
|
|
|
name = factory.Faker("catch_phrase")
|
|
slug = factory.LazyAttribute(lambda o: slugify(o.name))
|
|
access_level = factory.fuzzy.FuzzyChoice(models.RoomAccessLevel)
|
|
|
|
|
|
class RecordingFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake recording for testing."""
|
|
|
|
class Meta:
|
|
model = models.Recording
|
|
skip_postgeneration_save = True
|
|
|
|
room = factory.SubFactory(RoomFactory)
|
|
status = models.RecordingStatusChoices.INITIATED
|
|
mode = models.RecordingModeChoices.SCREEN_RECORDING
|
|
worker_id = None
|
|
|
|
@factory.post_generation
|
|
def users(self, create, extracted, **kwargs):
|
|
"""Add users to recording from a given list of users with or without roles."""
|
|
if create and extracted:
|
|
for item in extracted:
|
|
if isinstance(item, models.User):
|
|
UserRecordingAccessFactory(recording=self, user=item)
|
|
else:
|
|
UserRecordingAccessFactory(
|
|
recording=self, user=item[0], role=item[1]
|
|
)
|
|
|
|
self.save()
|
|
|
|
|
|
class UserRecordingAccessFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake recording user accesses for testing."""
|
|
|
|
class Meta:
|
|
model = models.RecordingAccess
|
|
|
|
recording = factory.SubFactory(RecordingFactory)
|
|
user = factory.SubFactory(UserFactory)
|
|
role = factory.fuzzy.FuzzyChoice(models.RoleChoices.values)
|
|
|
|
|
|
class TeamRecordingAccessFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake recording team accesses for testing."""
|
|
|
|
class Meta:
|
|
model = models.RecordingAccess
|
|
|
|
recording = factory.SubFactory(RecordingFactory)
|
|
team = factory.Sequence(lambda n: f"team{n}")
|
|
role = factory.fuzzy.FuzzyChoice(models.RoleChoices.values)
|
|
|
|
|
|
class ApplicationFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake applications for testing."""
|
|
|
|
class Meta:
|
|
model = models.Application
|
|
|
|
name = factory.Faker("company")
|
|
active = True
|
|
client_id = factory.LazyFunction(utils.generate_client_id)
|
|
client_secret = factory.LazyFunction(utils.generate_client_secret)
|
|
scopes = []
|
|
|
|
class Params:
|
|
"""Factory traits for common application configurations."""
|
|
|
|
with_all_scopes = factory.Trait(
|
|
scopes=[
|
|
models.ApplicationScope.ROOMS_LIST,
|
|
models.ApplicationScope.ROOMS_RETRIEVE,
|
|
models.ApplicationScope.ROOMS_CREATE,
|
|
models.ApplicationScope.ROOMS_UPDATE,
|
|
models.ApplicationScope.ROOMS_DELETE,
|
|
]
|
|
)
|
|
|
|
|
|
class ApplicationDomainFactory(factory.django.DjangoModelFactory):
|
|
"""Create fake application domains for testing."""
|
|
|
|
class Meta:
|
|
model = models.ApplicationDomain
|
|
|
|
domain = factory.Faker("domain_name")
|
|
application = factory.SubFactory(ApplicationFactory)
|