✨(oidc) add django-oauth-toolkit w/ configuration
This allows to use `people` as an identity provider using OIDC and local users. This commit is partial, because it does not manage a way to create "local" users and the login page is the admin one, which can't be used for non staff users or login with email.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Admin classes and registrations for People's mailbox manager app."""
|
||||
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.html import format_html_join, mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -163,7 +164,7 @@ class MailDomainAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(models.Mailbox)
|
||||
class MailboxAdmin(admin.ModelAdmin):
|
||||
class MailboxAdmin(UserAdmin):
|
||||
"""Admin for mailbox model."""
|
||||
|
||||
list_display = ("__str__", "domain", "status", "updated_at")
|
||||
@@ -171,6 +172,29 @@ class MailboxAdmin(admin.ModelAdmin):
|
||||
search_fields = ("local_part", "domain__name")
|
||||
readonly_fields = ["updated_at", "local_part", "domain"]
|
||||
|
||||
fieldsets = None
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": (
|
||||
"first_name",
|
||||
"last_name",
|
||||
"local_part",
|
||||
"domain",
|
||||
"secondary_email",
|
||||
"status",
|
||||
"usable_password",
|
||||
"password1",
|
||||
"password2",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
ordering = ("local_part", "domain")
|
||||
filter_horizontal = ()
|
||||
|
||||
|
||||
@admin.register(models.MailDomainAccess)
|
||||
class MailDomainAccessAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
from rest_framework import exceptions, serializers
|
||||
|
||||
@@ -33,8 +35,16 @@ class MailboxSerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
Override create function to fire a request on mailbox creation.
|
||||
|
||||
By default, we generate an unusable password for the mailbox, meaning that the mailbox
|
||||
will not be able to be used as a login credential until the password is set.
|
||||
"""
|
||||
mailbox = super().create(validated_data)
|
||||
mailbox = super().create(
|
||||
validated_data
|
||||
| {
|
||||
"password": make_password(None), # generate an unusable password
|
||||
}
|
||||
)
|
||||
if mailbox.domain.status == enums.MailDomainStatusChoices.ENABLED:
|
||||
client = DimailAPIClient()
|
||||
# send new mailbox request to dimail
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Mailbox manager application factories
|
||||
"""
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.utils.text import slugify
|
||||
|
||||
import factory.fuzzy
|
||||
@@ -76,6 +77,7 @@ class MailboxFactory(factory.django.DjangoModelFactory):
|
||||
)
|
||||
domain = factory.SubFactory(MailDomainEnabledFactory)
|
||||
secondary_email = factory.Faker("email")
|
||||
password = make_password("password")
|
||||
|
||||
|
||||
class MailboxEnabledFactory(MailboxFactory):
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-10 11:10
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0010_team_depth_team_numchild_team_path_and_more'),
|
||||
('mailbox_manager', '0017_alter_maildomain_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='maildomain',
|
||||
name='organization',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='mail_domains', to='core.organization'),
|
||||
),
|
||||
]
|
||||
@@ -19,7 +19,7 @@ def fill_dn_email(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailbox_manager', '0020_maildomain_organization'),
|
||||
('mailbox_manager', '0022_maildomain_organization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -3,6 +3,7 @@ Declare and configure the models for the People additional application : mailbox
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.core import exceptions, validators
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
@@ -94,6 +95,14 @@ class MailDomain(BaseModel):
|
||||
"manage_accesses": is_owner_or_admin,
|
||||
}
|
||||
|
||||
def is_identity_provider_ready(self) -> bool:
|
||||
"""
|
||||
Check if the identity provider is ready to manage the domain.
|
||||
"""
|
||||
return (
|
||||
bool(self.organization) and self.status == MailDomainStatusChoices.ENABLED
|
||||
)
|
||||
|
||||
|
||||
class MailDomainAccess(BaseModel):
|
||||
"""Allow to manage users' accesses to mail domains."""
|
||||
@@ -188,7 +197,7 @@ class MailDomainAccess(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class Mailbox(BaseModel):
|
||||
class Mailbox(AbstractBaseUser, BaseModel):
|
||||
"""Mailboxes for users from mail domain."""
|
||||
|
||||
first_name = models.CharField(max_length=200, blank=False)
|
||||
@@ -216,6 +225,13 @@ class Mailbox(BaseModel):
|
||||
default=MailboxStatusChoices.PENDING,
|
||||
)
|
||||
|
||||
# Store the denormalized email address to allow Django admin to work (USERNAME_FIELD)
|
||||
# This field *must* not be used for authentication (or anything sensitive),
|
||||
# use the `local_part` and `domain__name` fields
|
||||
dn_email = models.EmailField(_("email"), blank=True, unique=True, editable=False)
|
||||
|
||||
USERNAME_FIELD = "dn_email"
|
||||
|
||||
class Meta:
|
||||
db_table = "people_mail_box"
|
||||
verbose_name = _("Mailbox")
|
||||
@@ -241,9 +257,19 @@ class Mailbox(BaseModel):
|
||||
Override save function to not allow to create or update mailbox of a disabled domain.
|
||||
"""
|
||||
self.full_clean()
|
||||
self.dn_email = self.get_email()
|
||||
|
||||
if self.domain.status == MailDomainStatusChoices.DISABLED:
|
||||
raise exceptions.ValidationError(
|
||||
_("You can't create or update a mailbox for a disabled domain.")
|
||||
)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""Return True if the mailbox is enabled."""
|
||||
return self.status == MailboxStatusChoices.ENABLED
|
||||
|
||||
def get_email(self):
|
||||
"""Return the email address of the mailbox."""
|
||||
return f"{self.local_part}@{self.domain.name}"
|
||||
|
||||
@@ -8,6 +8,7 @@ from email.headerregistry import Address
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core import exceptions, mail
|
||||
from django.template.loader import render_to_string
|
||||
@@ -341,6 +342,7 @@ class DimailAPIClient:
|
||||
# secondary email is mandatory. Unfortunately, dimail doesn't
|
||||
# store any. We temporarily give current email as secondary email.
|
||||
status=enums.MailboxStatusChoices.ENABLED,
|
||||
password=make_password(None), # unusable password
|
||||
)
|
||||
imported_mailboxes.append(str(mailbox))
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user