From f067688e5dff0a18d5c6b80f5f0a47d525c2857d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Wed, 9 Oct 2019 20:54:14 +0200
Subject: [PATCH] cartads cs: sync subscribers with authentic role (#36888)

---
 passerelle/apps/cartads_cs/models.py | 36 ++++++++++++++++++++++++++++
 tests/test_cartads_cs.py             | 27 +++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/passerelle/apps/cartads_cs/models.py b/passerelle/apps/cartads_cs/models.py
index 13fc0bc4..3844de2d 100644
--- a/passerelle/apps/cartads_cs/models.py
+++ b/passerelle/apps/cartads_cs/models.py
@@ -37,6 +37,7 @@ from django.core.urlresolvers import reverse
 from django.db import models
 from django.http import HttpResponse
 from django.utils.encoding import force_text
+from django.utils.text import slugify
 from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 from django.utils.six.moves.urllib import parse as urlparse
@@ -46,6 +47,7 @@ import zeep.exceptions
 import zeep.helpers as zeep_helpers
 
 from passerelle.base.models import BaseResource
+from passerelle.base.signature import sign_url
 from passerelle.utils.api import endpoint
 from passerelle.utils.jsonresponse import APIError, JSONEncoder
 
@@ -321,6 +323,7 @@ class AbstractCartaDSCS(BaseResource):
             except zeep.exceptions.Fault as e:
                 self.logger.exception('error getting dossier infos (%s) (%r)', dossier.id, e.message)
             dossier.save()
+            self.sync_subscribers_role(dossier)
 
     def hourly(self):
         super(AbstractCartaDSCS, self).hourly()
@@ -790,6 +793,7 @@ class AbstractCartaDSCS(BaseResource):
         if notification.find('Succes').text == 'true':
             dossier.cartads_id_dossier = notification.find('InformationsComplementaires/IdDossierCartads').text
             dossier.cartads_numero_dossier = notification.find('InformationsComplementaires/NumeroDossier').text
+            self.sync_subscribers_role(dossier)
         dossier.save()
         return HttpResponse('ok', content_type='text/plain')
 
@@ -1059,6 +1063,7 @@ class AbstractCartaDSCS(BaseResource):
             dossier.save()
 
         dossier.subscribers.add(CartaDSSubscriber.objects.get_or_create(name_id=name_id)[0])
+        self.sync_subscribers_role(dossier)
         return {'err': 0,
                 'dossier_id': dossier.id,
                 'formdata_url': dossier.formdata_url,
@@ -1089,8 +1094,39 @@ class AbstractCartaDSCS(BaseResource):
         if subscriber not in dossier.subscribers.all():
             raise APIError('subscriber not subscribed to that dossier')
         dossier.subscribers.remove(subscriber)
+        self.sync_subscribers_role(dossier)
         return {'err': 0, 'dossier_id': dossier.id}
 
+    def sync_subscribers_role(self, dossier):
+        if not getattr(settings, 'KNOWN_SERVICES', {}).get('authentic'):
+            return
+        dossier_number = dossier.cartads_numero_dossier
+        idp_service = settings.KNOWN_SERVICES['authentic'].values()[0]
+        # sync subscribers with an authentic role, this can fail and it will
+        # be retried again later.
+        role_api_url = sign_url(
+                urlparse.urljoin(
+                    idp_service['url'],
+                    'api/roles/?get_or_create=slug&orig=%s' % idp_service.get('orig')),
+                key=idp_service.get('secret'))
+        response = self.requests.post(role_api_url,
+                json={'name': 'Suivi Cart@DS %s' % dossier_number,
+                      'slug': '_cartads_%s' % slugify(dossier_number),
+                })
+        role_uuid = response.json()['uuid']
+        role_api_url = sign_url(
+                urlparse.urljoin(
+                    idp_service['url'],
+                    'api/roles/%s/relationships/members/?orig=%s' % (
+                        role_uuid,
+                        idp_service.get('orig')
+                    )),
+                key=idp_service.get('secret'))
+        response = self.requests.post(role_api_url,
+                json={'data': [{'uuid': x.name_id} for x in dossier.subscribers.all()]}
+                )
+        return {}
+
 
 class CartaDSCS(AbstractCartaDSCS):
     category = _('Misc')
diff --git a/tests/test_cartads_cs.py b/tests/test_cartads_cs.py
index 5021a759..6368562c 100644
--- a/tests/test_cartads_cs.py
+++ b/tests/test_cartads_cs.py
@@ -2,6 +2,7 @@
 
 from collections import OrderedDict
 import datetime
+import json
 import os
 
 import mock
@@ -9,6 +10,7 @@ from httmock import HTTMock
 import pytest
 
 from django.core.files.storage import default_storage
+from django.test import override_settings
 
 from passerelle.apps.cartads_cs.models import CartaDSCS, CartaDSFile, CartaDSDossier
 from passerelle.base.models import Job
@@ -637,3 +639,28 @@ def test_unsubscribe(connector, app, cached_data):
         assert resp.json['err'] == 1
         resp = app.get('/cartads-cs/test/unsubscribe?name_id=4567&dossier_number=434')
         assert resp.json['err'] == 1
+
+
+def test_role_sync(connector, app, cached_data):
+    def idp_mock(url, request):
+        assert url.netloc == 'idp.example.org'
+        if url.path == '/api/roles/':
+            assert request.body == '{"name": "Suivi Cart@DS 123", "slug": "_cartads_123"}'
+            return {'content': json.dumps({'uuid': 'role-uuid'}), 'status_code': 200}
+        elif url.path == '/api/roles/role-uuid/relationships/members/':
+            body = json.loads(request.body)
+            idp_mock.subscribed_roles = set([x['uuid'] for x in body['data']])
+            return {'content': json.dumps({'err': 0}), 'status_code': 200}
+        raise Exception('unhandled http call (%s)' % url)
+
+    with HTTMock(idp_mock), override_settings(
+            KNOWN_SERVICES={
+                'authentic': {
+                    'idp': {
+                        'url': 'http://idp.example.org/',
+                        'verif_orig': 'abc',
+                        'secret': 'def',
+                        }
+            }}):
+        test_join(connector, app, cached_data)
+        assert idp_mock.subscribed_roles == set(['2345', '3456'])
-- 
2.23.0

