-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨(oidc) people as an identity provider #638
base: main
Are you sure you want to change the base?
Conversation
799ef24
to
3a2f5a4
Compare
fba081c
to
35a46ae
Compare
COMMENTAIRE ORAL QUENTIN |
18a46da
to
89ac911
Compare
Changelog will be added when review is done (to ease the rebase process...) |
15126b7
to
205f12c
Compare
assert user is None | ||
|
||
|
||
def test_get_username_domain_from_email_valid(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps here we could use @pytest.mark.parametrize
and include more test cases? Ideas:
- Unicode only in username or domain
- Spaces
- Very long strings
- weird characters (\0 ?)
- empty username
- empty domain
- quotes, commas, ..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely 👍
Organization, | ||
on_delete=models.PROTECT, | ||
related_name="mail_domains", | ||
null=True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the plan to later switch this to null=False ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A domain doesn't really require an Organization today, it's only mandatory for ProConnect for now... But when we will improve permissions, it may become:
- easy to determine the organization, or at least propose a set of organization to the end user
- mandatory to have an organization to be able to determine who can manage mailboxes
So yeah, it may become null=False in a near future :) While the "near future" is not clear, I did not mention it in the code ^^'
@@ -341,6 +342,7 @@ def import_mailboxes(self, domain): | |||
# 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a comment. Are we absolutely sure it can't lead to successful logins with empty passwords?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, when None
it starts with the "invalid password" prefix.
See https://github.com/django/django/blob/5.1.6/django/contrib/auth/hashers.py#L103-L106
and https://github.com/django/django/blob/5.1.6/django/contrib/auth/hashers.py#L28-L42
I will add a comment. I don't like testing the framework but I can test "None" password cannot login, just in case.
@@ -34,7 +36,7 @@ def create(self, validated_data): | |||
""" | |||
Override create function to fire a request on mailbox creation. | |||
""" | |||
mailbox = super().create(validated_data) | |||
mailbox = super().create(validated_data | {"password": make_password(None)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same feedback here
@@ -202,6 +218,9 @@ class Mailbox(BaseModel): | |||
choices=MailboxStatusChoices.choices, | |||
default=MailboxStatusChoices.PENDING, | |||
) | |||
dn_email = models.EmailField(_("email"), blank=True, unique=True, editable=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is dn_ ? isn't it a potential issue to store both this and the local_part ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"dn_" stands for "denormalized". In the first place, I tried to add a generated field but it does not work with "join" (it was a concat of "local_part", "@" and "domain__name" 💥 ), so I used a field which is computed on save to store the denormalized email value.
For sure, if you make Mailbox.objects.filter().update(local_part="blah")
, the dn_email
will be broken...
The dn_email
is required to make the Django admin work for mailbox with password...
I can add a comment to say we should only use this field for the Django admin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be a reason to do the reverse and compute local_part while only storing the full email?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but it implies a bit of work, if we need it we may do it in another PR if it's ok for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sylvinus Thanks for the review, I will improve that :)
@@ -341,6 +342,7 @@ def import_mailboxes(self, domain): | |||
# 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, when None
it starts with the "invalid password" prefix.
See https://github.com/django/django/blob/5.1.6/django/contrib/auth/hashers.py#L103-L106
and https://github.com/django/django/blob/5.1.6/django/contrib/auth/hashers.py#L28-L42
I will add a comment. I don't like testing the framework but I can test "None" password cannot login, just in case.
assert user is None | ||
|
||
|
||
def test_get_username_domain_from_email_valid(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely 👍
Organization, | ||
on_delete=models.PROTECT, | ||
related_name="mail_domains", | ||
null=True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A domain doesn't really require an Organization today, it's only mandatory for ProConnect for now... But when we will improve permissions, it may become:
- easy to determine the organization, or at least propose a set of organization to the end user
- mandatory to have an organization to be able to determine who can manage mailboxes
So yeah, it may become null=False in a near future :) While the "near future" is not clear, I did not mention it in the code ^^'
@@ -202,6 +218,9 @@ class Mailbox(BaseModel): | |||
choices=MailboxStatusChoices.choices, | |||
default=MailboxStatusChoices.PENDING, | |||
) | |||
dn_email = models.EmailField(_("email"), blank=True, unique=True, editable=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"dn_" stands for "denormalized". In the first place, I tried to add a generated field but it does not work with "join" (it was a concat of "local_part", "@" and "domain__name" 💥 ), so I used a field which is computed on save to store the denormalized email value.
For sure, if you make Mailbox.objects.filter().update(local_part="blah")
, the dn_email
will be broken...
The dn_email
is required to make the Django admin work for mailbox with password...
I can add a comment to say we should only use this field for the Django admin.
# Check if the account is locked | ||
if self._check_login_attempts(email): | ||
logger.warning("Account locked due to too many failed attempts: %s", email) | ||
Mailbox().set_password(password) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use the same comment as below (moreover, it's the first). great technique btw!
assert response.status_code == 200 | ||
|
||
# assert the user has a session | ||
assert not client.session.is_empty() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random thought, is there an expiration time on this session? it should always be short-lived, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, even if the session cannot be used in any other context, I reduced it to one minute.
Great patch congrats! Given the sensitive nature of providing OIDC identities, I think we need to do a careful security review. For instance, we should look into security headers on the login page (https://www.cerberauth.com/blog/openid-connect-provider-security-headers/) Potential list (for next and/or django?)
|
@sylvinus In production, the Django headers are already quite well set, see for instance on /api/v1.0/authenticate/:
|
205f12c
to
52fcb60
Compare
docs/identityProvider.md
Outdated
|
||
### Password strength | ||
|
||
There is currently no constraint on the password strength has it can only be defined by Django administrators, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is currently no constraint on the password strength has it can only be defined by Django administrators, | |
There is currently no constraint on the password strength as it can only be defined by Django administrators, |
src/backend/mailbox_manager/admin.py
Outdated
"""Admin for mailbox model.""" | ||
|
||
list_display = ("__str__", "domain", "status", "updated_at") | ||
list_filter = ("status",) | ||
search_fields = ("local_part", "domain__name") | ||
readonly_fields = ["updated_at", "local_part", "domain"] | ||
# readonly_fields = ["updated_at", "local_part", "domain"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead code or should be readonly?
src/backend/mailbox_oauth2/views.py
Outdated
email = request.data.get("email") | ||
password = request.data.get("password") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ot use a serializer? It could validate that what is submitted is an email and validate password format, returning proper errors etc.?
775f3bf
to
17bdd09
Compare
To be able to provide a SIRET in the ProConnect IdP process we need to be able to link a mail domain to its organization. For now this is not mandatory, as we can't detect the organization and need a frontend process to clarify it.
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 configures local environment to test login through people: - Keycloak configuration of the IdP (people) - Add Keycloak Application in people The only user who can login for now is "admin".
To have a better user experience, we want the login page to in the frontend.
Few fixes to allow the keycloak dev stack to use people as an Identity Provider. This requires the update of the bitnami keycloak chart we use.
This is making too much noise when developing using the tilt stack...
This is a simple test to assert a user can login via people when setup as an identity provider.
This provides a light documentation about the way to configure people as an IdentityProvider.
17bdd09
to
d7bfdc4
Compare
Purpose
We want the people user database to also be an Identity Provider.
Ref:
Proposal
This is the very first step toward this goal:
acr
to refuse higher level thaneidas1
when requested by ProConnect (comes from the SP)login_hint
for the login page (and don't allow email field edition?)