Skip to content

Commit

Permalink
fixup! ✨(mailboxes) add webhook to dimail-api
Browse files Browse the repository at this point in the history
  • Loading branch information
mjeammet committed Jun 28, 2024
1 parent 71b42a5 commit 7bdd1d4
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 245 deletions.
5 changes: 2 additions & 3 deletions src/backend/mailbox_manager/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class Meta:

domain = factory.SubFactory(MailDomainFactory)
url = factory.LazyAttribute(
lambda o: f"https://example.com/api/domains/{domain.name}/mailboxes/"
% o.domain.name
lambda o: "https://example.com/api/domains/%s/mailboxes/" % o.domain.name
)
secret = "encoded_secret" # noqa: S105
secret = factory.Faker("password")
40 changes: 37 additions & 3 deletions src/backend/mailbox_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,25 @@
from django.utils.translation import gettext_lazy as _

import requests
import logging

import requests
from urllib3.util import Retry
from core.models import BaseModel, RoleChoices, WebhookStatusChoices

from mailbox_manager.utils.webhooks import scim_synchronizer
logger = logging.getLogger(__name__)

adapter = requests.adapters.HTTPAdapter(
max_retries=Retry(
total=4,
backoff_factor=0.1,
status_forcelist=[500, 502],
allowed_methods=["PATCH"],
)
)

session = requests.Session()
session.mount("http://", adapter)


class MailDomain(BaseModel):
Expand Down Expand Up @@ -131,15 +146,34 @@ def save(self, *args, **kwargs):
"""

if self._state.adding:
self.domain.webhooks.update(status=WebhookStatusChoices.PENDING)
with transaction.atomic():
self.create_mailbox(self.local_part)
instance = super().save(*args, **kwargs)
scim_synchronizer.create_mailbox(self.domain, self.local_part)
else:
instance = super().save(*args, **kwargs)

return instance

def create_mailbox(self, local_part):
"""Create a mailbox on webhook's domain."""

payload = {
"email": f"{local_part}@{self.domain}",
"givenName": local_part,
"surName": "Test",
"displayName": f"{local_part} Test",
}

return session.post(
f"{settings.MAIL_PROVISIONER_URL}/domains/{self.domain}/mailboxes/",
json=payload,
headers=webhook.get_headers(),
# verify=False,
verify=True,
# verify=self.get_settings("OIDC_VERIFY_SSL", True),
timeout=10,
)


class MailDomainWebhook(BaseModel):
"""Webhooks fired on changes in domains."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,60 @@ def test_api_mailboxes__create_authenticated_successful():
assert mailbox.secondary_email == mailbox_data["secondary_email"]


def test_api_mailboxes__webhook_dont_fire_unauthorized():
"""
Webhook should not be fired if unauthorized user try to create a mailbox.
"""
# creating all necessary objects
domain = factories.MailDomainFactory()
webhook = factories.MailDomainWebhookFactory(domain=domain)
mailbox_data = serializers.MailboxSerializer(factories.MailboxFactory.build()).data

client = APIClient()
client.force_login(core_factories.UserFactory()) # user with no access

with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
rsp = rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
status=200,
content_type="application/json",
)
rsp = rsps.add(
rsps.POST,
re.compile(rf".*/api/domains/{domain.name}/mailboxes/"),
body='{"detail": "Permission denied"}',
status=401,
content_type="application/json",
)

response = client.post(
f"/api/v1.0/mail-domains/{domain.id}/mailboxes/",
mailbox_data,
format="json",
)
assert response.status_code == 201 # a fix après la PR de Sabrina
assert rsp.call_count == 1
assert rsps.calls[1].request.url == webhook.url # rsps.calls[0] is the token


def test_api_mailboxes__webhook_fire_upon_create():
"""
When the domain has a webhook, creating a mailbox should fire a call.
"""

# creating all needed objects
domain = factories.MailDomainFactory()
access = factories.MailDomainAccessFactory(domain=domain)
webhook = factories.MailDomainWebhookFactory(domain=domain)
mailbox_data = serializers.MailboxSerializer(factories.MailboxFactory.build()).data
mailbox_data = serializers.MailboxSerializer(
factories.MailboxFactory.build(domain=domain)
).data

client = APIClient()
client.force_login(core_factories.UserFactory())
client.force_login(access.user)

with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
Expand Down
176 changes: 59 additions & 117 deletions src/backend/mailbox_manager/tests/test_utils_webhooks_scim_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Test Team synchronization webhooks."""

import random
import re
from logging import Logger
from unittest import mock

import pytest
import responses

Expand All @@ -20,123 +25,60 @@ def test_utils_webhooks_add_mailbox_to_domain__no_webhooks():
assert len(responses.calls) == 0


# @mock.patch.object(Logger, "info")
# def test_utils_webhooks_add_user_to_group_success(mock_info):
# """The user passed to the function should get added."""
# identity = factories.IdentityFactory()
# access = factories.TeamAccessFactory(user=identity.user)
# webhooks = factories.TeamWebhookFactory.create_batch(2, team=access.team)

# with responses.RequestsMock() as rsps:
# # Ensure successful response by scim provider using "responses":
# rsps.add(
# rsps.PATCH,
# re.compile(r".*/Groups/.*"),
# body="{}",
# status=200,
# content_type="application/json",
# )

# scim_synchronizer.add_user_to_group(access.team, access.user)

# for i, webhook in enumerate(webhooks):
# assert rsps.calls[i].request.url == webhook.url

# # Check headers
# headers = rsps.calls[i].request.headers
# assert "Authorization" not in headers
# assert headers["Content-Type"] == "application/json"

# # Payload sent to scim provider
# for call in rsps.calls:
# payload = json.loads(call.request.body)
# assert payload == {
# "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
# "Operations": [
# {
# "op": "add",
# "path": "members",
# "value": [
# {
# "value": str(access.user.id),
# "email": identity.email,
# "type": "User",
# }
# ],
# }
# ],
# }

# # Logger
# assert mock_info.call_count == 2
# for i, webhook in enumerate(webhooks):
# assert mock_info.call_args_list[i][0] == (
# "%s synchronization succeeded with %s",
# "add_user_to_group",
# webhook.url,
# )

# # Status
# for webhook in webhooks:
# webhook.refresh_from_db()
# assert webhook.status == "success"


# @mock.patch.object(Logger, "info")
# def test_utils_webhooks_remove_user_from_group_success(mock_info):
# """The user passed to the function should get removed."""
# identity = factories.IdentityFactory()
# access = factories.TeamAccessFactory(user=identity.user)
# webhooks = factories.TeamWebhookFactory.create_batch(2, team=access.team)

# with responses.RequestsMock() as rsps:
# # Ensure successful response by scim provider using "responses":
# rsps.add(
# rsps.PATCH,
# re.compile(r".*/Groups/.*"),
# body="{}",
# status=200,
# content_type="application/json",
# )

# scim_synchronizer.remove_user_from_group(access.team, access.user)

# for i, webhook in enumerate(webhooks):
# assert rsps.calls[i].request.url == webhook.url

# # Payload sent to scim provider
# for call in rsps.calls:
# payload = json.loads(call.request.body)
# assert payload == {
# "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
# "Operations": [
# {
# "op": "remove",
# "path": "members",
# "value": [
# {
# "value": str(access.user.id),
# "email": identity.email,
# "type": "User",
# }
# ],
# }
# ],
# }

# # Logger
# assert mock_info.call_count == 2
# for i, webhook in enumerate(webhooks):
# assert mock_info.call_args_list[i][0] == (
# "%s synchronization succeeded with %s",
# "remove_user_from_group",
# webhook.url,
# )

# # Status
# for webhook in webhooks:
# webhook.refresh_from_db()
# assert webhook.status == "success"
@mock.patch.object(Logger, "info")
def test_utils_webhooks__create_mailbox_success(mock_info):
"""The user passed to the function should get added."""
domain = factories.MailDomainFactory()
webhooks = factories.TeamWebhookFactory.create_batch(2, team=access.team)

with responses.RequestsMock() as rsps:
# Ensure successful response by scim provider using "responses":
sp = rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
status=200,
content_type="application/json",
)
rsp = rsps.add(
rsps.POST,
re.compile(rf".*/api/domains/{domain.name}/mailboxes/"),
body='{"email": f"{mailbox_data.local_part}@{mailbox_data.domain.name}", "password": "something_mysterieux", "uuid": "SECURITY}',
status=201,
content_type="application/json",
)

scim_synchronizer.create_mailbox(self.domain, self.local_part)

assert rsps.calls[i].request.url == webhook.url

# Check headers
headers = rsps.calls[i].request.headers
assert "Authorization" not in headers
assert headers["Content-Type"] == "application/json"

# Payload sent to mailbox provider
payload = json.loads(call.request.body)
assert payload == {
"displayName": f'{mailbox_data["local_part"]} Test',
"email": f'{mailbox_data["local_part"]}@{domain.name}',
"givenName": f'{mailbox_data["local_part"]}',
"surName": "Test",
}

# Logger
assert mock_info.call_count == 2
for i, webhook in enumerate(webhooks):
assert mock_info.call_args_list[i][0] == (
"%s synchronization succeeded with %s",
"add_user_to_group",
webhook.url,
)

# Status
for webhook in webhooks:
webhook.refresh_from_db()
assert webhook.status == "success"


# @mock.patch.object(Logger, "error")
Expand Down
Empty file.
45 changes: 0 additions & 45 deletions src/backend/mailbox_manager/utils/scim.py

This file was deleted.

Loading

0 comments on commit 7bdd1d4

Please sign in to comment.