Projet

Général

Profil

0001-general-use-HTTP-API-to-provision-users-groups-43245.patch

Frédéric Péters, 27 mai 2020 15:00

Télécharger (38,7 ko)

Voir les différences:

Subject: [PATCH] general: use HTTP API to provision users & groups (#43245)

 debian/debian_config_common.py                |   2 +
 hobo/agent/authentic2/provisionning.py        |  39 ++++-
 .../common/management/commands/hobo_notify.py | 141 +---------------
 hobo/environment/models.py                    |   8 +
 hobo/environment/utils.py                     |  44 +++--
 hobo/multitenant/settings_loaders.py          |   2 +
 hobo/provisionning/__init__.py                |   0
 hobo/provisionning/middleware.py              |  81 +++++++++
 hobo/provisionning/utils.py                   | 159 ++++++++++++++++++
 tests/settings.py                             |   6 +-
 tests_authentic/test_provisionning.py         |  58 +++++++
 tests_multitenant/test_settings.py            |   2 +-
 tests_schemas/example_env.json                |  22 ++-
 13 files changed, 397 insertions(+), 167 deletions(-)
 create mode 100644 hobo/provisionning/__init__.py
 create mode 100644 hobo/provisionning/middleware.py
 create mode 100644 hobo/provisionning/utils.py
debian/debian_config_common.py
264 264
    if PROJECT_NAME != 'wcs' and 'authentic2' not in INSTALLED_APPS:
265 265
        MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
266 266
            'mellon.middleware.PassiveAuthenticationMiddleware',
267
            'hobo.provisionning.middleware.ProvisionningMiddleware',
267 268
        )
268 269

  
269 270
    if 'authentic2' in INSTALLED_APPS:
......
282 283
    if PROJECT_NAME != 'wcs' and 'authentic2' not in INSTALLED_APPS:
283 284
        MIDDLEWARE = MIDDLEWARE + (
284 285
            'mellon.middleware.PassiveAuthenticationMiddleware',
286
            'hobo.provisionning.middleware.ProvisionningMiddleware',
285 287
        )
286 288

  
287 289
    if 'authentic2' in INSTALLED_APPS:
hobo/agent/authentic2/provisionning.py
3 3
import threading
4 4
import copy
5 5
import logging
6
import requests
6 7

  
7 8
from django.contrib.auth import get_user_model
8 9
from django.db import connection
......
12 13

  
13 14
from django_rbac.utils import get_role_model, get_ou_model, get_role_parenting_model
14 15
from hobo.agent.common import notify_agents
16
from hobo.signature import sign_url
15 17
from authentic2.saml.models import LibertyProvider
16 18
from authentic2.a2_rbac.models import RoleAttribute
17 19
from authentic2.models import AttributeValue
......
164 166
                    for service, audience in self.get_audience(ou):
165 167
                        for user in users:
166 168
                            logger.info(u'provisionning user %s to %s', user, audience)
167
                            notify_agents({
169
                            self.notify_agents({
168 170
                                '@type': 'provision',
169 171
                                'issuer': issuer,
170 172
                                'audience': [audience],
......
181 183
                        continue
182 184
                    logger.info(u'provisionning users %s to %s', u', '.join(
183 185
                        map(force_text, users)), u', '.join(audience))
184
                    notify_agents({
186
                    self.notify_agents({
185 187
                        '@type': 'provision',
186 188
                        'issuer': issuer,
187 189
                        'audience': audience,
......
196 198
                        for s, audience in self.get_audience(ou)]
197 199
            logger.info(u'deprovisionning users %s from %s', u', '.join(
198 200
                map(force_text, users)), u', '.join(audience))
199
            notify_agents({
201
            self.notify_agents({
200 202
                '@type': 'deprovision',
201 203
                'issuer': issuer,
202 204
                'audience': audience,
......
249 251

  
250 252
            audience = [entity_id for service, entity_id in self.get_audience(ou)]
251 253
            logger.info(u'%sning roles %s to %s', mode, roles, audience)
252
            notify_agents({
254
            self.notify_agents({
253 255
                '@type': mode,
254 256
                'audience': audience,
255 257
                'full': full,
......
401 403
                if not reverse:
402 404
                    for other_instance in instance.members.all():
403 405
                        self.add_saved(other_instance)
406

  
407
    def notify_agents(self, data):
408
        if getattr(settings, 'HOBO_HTTP_PROVISIONNING', False):
409
            services_by_url = {}
410
            for services in settings.KNOWN_SERVICES.values():
411
                for service in services.values():
412
                    if service.get('provisionning-url'):
413
                        services_by_url[service['saml-sp-metadata-url']] = service
414
            audience = data.get('audience')
415
            rest_audience = [x for x in audience if x in services_by_url]
416
            amqp_audience = audience
417
            for audience in rest_audience:
418
                service = services_by_url[audience]
419
                data['audience'] = [audience]
420
                try:
421
                    response = requests.put(
422
                        sign_url(service['provisionning-url'] + '?orig=%s' % service['orig'], service['secret']),
423
                        json=data)
424
                    response.raise_for_status()
425
                except requests.RequestException as e:
426
                    logger.error(u'error provisionning to %s (%s)', audience, e)
427
                else:
428
                    amqp_audience.remove(audience)
429
            data['audience'] = amqp_audience
430
            if amqp_audience:
431
                logger.info(u'leftover AMQP audience: %s', amqp_audience)
432

  
433
        if data['audience']:
434
            notify_agents(data)
hobo/agent/common/management/commands/hobo_notify.py
17 17
import json
18 18
import os
19 19
import sys
20
import random
21
import logging
22 20

  
23 21
from django.core.management.base import BaseCommand
24
from django.db.transaction import atomic
25
from django.db import IntegrityError
26 22

  
27 23
from tenant_schemas.utils import tenant_context
28 24
from hobo.multitenant.middleware import TenantMiddleware
29
from hobo.multitenant.utils import provision_user_groups
30 25

  
31
from hobo.agent.common.models import Role
26
from hobo.provisionning.utils import NotificationProcessing, TryAgain
32 27

  
33
class TryAgain(Exception):
34
    pass
35 28

  
36
class Command(BaseCommand):
29
class Command(BaseCommand, NotificationProcessing):
37 30
    requires_system_checks = False
38 31

  
39 32
    def add_arguments(self, parser):
......
55 48
            with tenant_context(tenant):
56 49
                self.process_notification(tenant, notification)
57 50

  
58
    @classmethod
59
    def check_valid_notification(cls, notification):
60
        return isinstance(notification, dict) \
61
            and '@type' in notification \
62
            and notification['@type'] in ['provision', 'deprovision'] \
63
            and 'objects' in notification \
64
            and 'audience' in notification \
65
            and isinstance(notification['audience'], list) \
66
            and isinstance(notification['objects'], dict)
67

  
68
    @classmethod
69
    def check_valid_role(cls, o):
70
        return 'uuid' in o \
71
               and 'name' in o \
72
               and 'description' in o
73

  
74
    @classmethod
75
    def check_valid_user(cls, o):
76
        return 'uuid' in o \
77
               and 'is_superuser' in o \
78
               and 'email' in o \
79
               and 'first_name' in o \
80
               and 'last_name' in o \
81
               and 'roles' in o
82

  
83
    @classmethod
84
    def provision_user(cls, issuer, action, data, full=False):
85
        from django.contrib.auth import get_user_model
86
        from mellon.models import UserSAMLIdentifier
87
        User = get_user_model()
88

  
89

  
90
        assert not full  # provisionning all users is dangerous, we prefer deprovision
91
        uuids = set()
92
        for o in data:
93
            try:
94
                with atomic():
95
                    if action == 'provision':
96
                        assert cls.check_valid_user(o)
97
                        try:
98
                            mellon_user = UserSAMLIdentifier.objects.get(
99
                                issuer=issuer, name_id=o['uuid'])
100
                            user = mellon_user.user
101
                        except UserSAMLIdentifier.DoesNotExist:
102
                            try:
103
                                user = User.objects.get(username=o['uuid'][:30])
104
                            except User.DoesNotExist:
105
                                # temp user object
106
                                random_uid = str(random.randint(1,10000000000000))
107
                                user = User.objects.create(
108
                                    username=random_uid)
109
                            mellon_user = UserSAMLIdentifier.objects.create(
110
                                user=user, issuer=issuer, name_id=o['uuid'])
111
                        user.first_name = o['first_name'][:30]
112
                        user.last_name = o['last_name'][:30]
113
                        user.email = o['email'][:75]
114
                        user.username = o['uuid'][:30]
115
                        user.is_superuser = o['is_superuser']
116
                        user.is_staff = o['is_superuser']
117
                        user.save()
118
                        role_uuids = [role['uuid'] for role in o.get('roles', [])]
119
                        provision_user_groups(user, role_uuids)
120
                    elif action == 'deprovision':
121
                        assert 'uuid' in o
122
                uuids.add(o['uuid'])
123
            except IntegrityError:
124
                raise TryAgain
125
        if full and action == 'provision':
126
            for usi in UserSAMLIdentifier.objects.exclude(name_id__in=uuids):
127
                usi.user.delete()
128
        elif action == 'deprovision':
129
            for user in User.objects.filter(saml_identifiers__name_id__in=uuids):
130
                user.delete()
131

  
132
    @classmethod
133
    def provision_role(cls, issuer, action, data, full=False):
134
        logger = logging.getLogger(__name__)
135
        uuids = set()
136
        for o in data:
137
            assert 'uuid' in o
138
            uuids.add(o['uuid'])
139
            if action == 'provision':
140
                assert cls.check_valid_role(o)
141
                role_name = o['name']
142
                if len(role_name) > 70:
143
                    role_name = role_name[:70] + '(...)'
144
                try:
145
                    role = Role.objects.get(uuid=o['uuid'])
146
                    created = False
147
                except Role.DoesNotExist:
148
                    try:
149
                        with atomic():
150
                            role, created = Role.objects.get_or_create(
151
                                name=role_name, defaults={
152
                                    'uuid': o['uuid'],
153
                                    'description': o['description']})
154
                    except IntegrityError:
155
                        # Can happen if uuid and name already exist
156
                        logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
157
                        continue
158
                if not created:
159
                    save = False
160
                    if role.name != role_name:
161
                        role.name = role_name
162
                        save = True
163
                    if role.uuid != o['uuid']:
164
                        role.uuid = o['uuid']
165
                        save = True
166
                    if role.description != o['description']:
167
                        role.description = o['description']
168
                        save = True
169
                    if role.details != o.get('details', u''):
170
                        role.details = o.get('details', u'')
171
                        save = True
172
                    if save:
173
                        try:
174
                            with atomic():
175
                                role.save()
176
                        except IntegrityError:
177
                            # Can happen if uuid and name already exist
178
                            logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
179
                            continue
180
        if full and action == 'provision':
181
            for role in Role.objects.exclude(uuid__in=uuids):
182
                role.delete()
183
        elif action == 'deprovision':
184
            for role in Role.objects.filter(uuid__in=uuids):
185
                role.delete()
186

  
187 51
    @classmethod
188 52
    def process_notification(cls, tenant, notification):
189 53
        assert cls.check_valid_notification(notification), \
......
197 61
        assert entity_id, 'service has no saml-sp-metadat-url field'
198 62
        if entity_id not in audience:
199 63
            return
200
        uuids = set()
201 64
        object_type = notification['objects']['@type']
202 65
        for i in range(20):
203 66
            try:
hobo/environment/models.py
155 155
            as_dict['saml-idp-metadata-url'] = self.get_saml_idp_metadata_url()
156 156
        if self.get_backoffice_menu_url():
157 157
            as_dict['backoffice-menu-url'] = self.get_backoffice_menu_url()
158
        if self.get_provisionning_url():
159
            as_dict['provisionning-url'] = self.get_provisionning_url()
158 160
        return as_dict
159 161

  
160 162
    @property
......
207 209
    def get_backoffice_menu_url(self):
208 210
        return None
209 211

  
212
    def get_provisionning_url(self):
213
        return self.get_base_url_path() + '__provision__/'
214

  
210 215
    def is_resolvable(self):
211 216
        try:
212 217
            netloc = urlparse(self.base_url).netloc
......
298 303
    def get_backoffice_menu_url(self):
299 304
        return self.get_base_url_path() + 'manage/menu.json'
300 305

  
306
    def get_provisionning_url(self):
307
        return None
308

  
301 309

  
302 310
class Wcs(ServiceBase):
303 311
    class Meta:
hobo/environment/utils.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import hashlib
18

  
17 19
from django.conf import settings
18 20
from django.urls import reverse
19 21
from django.db import connection
......
21 23
from django.utils.encoding import force_text
22 24

  
23 25
from hobo.middleware.utils import StoreRequestMiddleware
26
from hobo.multitenant.settings_loaders import KnownServices
24 27

  
25 28

  
26 29
def get_installed_services():
......
35 38
    return [x for x in get_installed_services() if x.is_operational()]
36 39

  
37 40

  
38
def get_installed_services_dict():
39
    from .models import Variable
40
    hobo_service = []
41
def get_local_key(url):
42
    secret1 = force_text(settings.SECRET_KEY)
43
    secret2 = url
44
    return KnownServices.shared_secret(secret1, secret2)[:40]
45

  
46

  
47
def get_local_hobo_dict():
41 48
    build_absolute_uri = None
42 49
    if hasattr(connection, 'get_tenant') and hasattr(connection.get_tenant(), 'build_absolute_uri'):
43 50
        build_absolute_uri = connection.get_tenant().build_absolute_uri
......
45 52
        request = StoreRequestMiddleware.get_request()
46 53
        if request:
47 54
            build_absolute_uri = request.build_absolute_uri
48
    if build_absolute_uri:
49
        # if there's a known base url hobo can advertise itself.
50
        hobo_service = [{
51
            'service-id': 'hobo',
52
            'title': 'Hobo',
53
            'slug': 'hobo',
54
            'base_url': build_absolute_uri(reverse('home')),
55
            'saml-sp-metadata-url': build_absolute_uri(reverse('mellon_metadata')),
56
            'backoffice-menu-url': build_absolute_uri(reverse('menu_json')),
57
        }]
55
    if not build_absolute_uri:
56
        return None
57
    # if there's a known base url hobo can advertise itself.
58
    return {
59
        'secret_key': get_local_key(build_absolute_uri('/')),
60
        'service-id': 'hobo',
61
        'title': 'Hobo',
62
        'slug': 'hobo',
63
        'base_url': build_absolute_uri(reverse('home')),
64
        'saml-sp-metadata-url': build_absolute_uri(reverse('mellon_metadata')),
65
        'backoffice-menu-url': build_absolute_uri(reverse('menu_json')),
66
        'provisionning-url': build_absolute_uri('/__provision__/'),
67
    }
68

  
69

  
70
def get_installed_services_dict():
71
    from .models import Variable
72
    hobo_service = []
73
    hobo_dict = get_local_hobo_dict()
74
    if hobo_dict:
75
        hobo_service.append(hobo_dict)
58 76
    return {
59 77
        'services': hobo_service + [x.as_dict() for x in get_installed_services()],
60 78
        'variables': {v.name: v.json for v in Variable.objects.filter(service_pk__isnull=True)}
hobo/multitenant/settings_loaders.py
84 84
            service_data = {
85 85
                'url': url,
86 86
                'backoffice-menu-url': service.get('backoffice-menu-url'),
87
                'provisionning-url': service.get('provisionning-url'),
88
                'saml-sp-metadata-url': service.get('saml-sp-metadata-url'),
87 89
                'title': service.get('title'),
88 90
                'orig': orig,
89 91
                'verif_orig': verif_orig,
hobo/provisionning/middleware.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import json
18

  
19
from django.conf import settings
20
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponseForbidden
21
from django.utils.encoding import force_text
22
from django.utils.deprecation import MiddlewareMixin
23
from django.utils.six.moves.urllib.parse import urlparse
24

  
25
from hobo.provisionning.utils import NotificationProcessing, TryAgain
26
from hobo.rest_authentication import PublikAuthentication, PublikAuthenticationFailed
27

  
28

  
29
class ProvisionningMiddleware(MiddlewareMixin, NotificationProcessing):
30
    def process_request(self, request):
31
        if not (request.method == 'PUT' and request.path == '/__provision__/'):
32
            return None
33
        if 'hobo.environment' in settings.INSTALLED_APPS:
34
            # much ado about hobo
35
            from hobo.environment.utils import get_local_hobo_dict
36
            known_services = getattr(settings, 'KNOWN_SERVICES', None)
37
            local_hobo_dict = get_local_hobo_dict()
38
            if not known_services:
39
                # hobo in a single deployment instance
40
                settings.KNOWN_SERVICES = known_services = {}
41
                known_services['hobo'] = {'hobo': local_hobo_dict}
42
                known_services['authentic'] = {'idp': {}}
43
            if known_services['hobo']['hobo']['provisionning-url'] == local_hobo_dict['provisionning-url']:
44
                # hobo in a single deployment instance, or primary hobo in a
45
                # multi-instances environment
46
                from hobo.environment.models import Authentic
47
                from hobo.multitenant.settings_loaders import KnownServices
48
                authentic = Authentic.objects.all().first()
49
                orig = urlparse(authentic.base_url).netloc.split(':')[0]
50
                # create stub settings.KNOWN_SERVICES with just enough to get
51
                # authentication passing.
52
                idp_service = list(settings.KNOWN_SERVICES['authentic'].values())[0]
53
                idp_service['verif_orig'] = orig
54
                idp_service['secret_key'] = KnownServices.shared_secret(
55
                        authentic.secret_key, local_hobo_dict['secret_key'])
56
        try:
57
            PublikAuthentication().authenticate(request)
58
        except PublikAuthenticationFailed:
59
            return HttpResponseForbidden()
60
        try:
61
            notification = json.loads(force_text(request.body))
62
        except ValueError:
63
            return HttpResponseBadRequest()
64
        if not isinstance(notification, dict) or 'objects' not in notification:
65
            return HttpResponseBadRequest()
66

  
67
        object_type = notification['objects'].get('@type')
68
        issuer = notification.get('issuer')
69
        action = notification.get('@type')
70
        if not (object_type and issuer and action):
71
            return HttpResponseBadRequest()
72
        full = notification['full'] if 'full' in notification else False
73

  
74
        for i in range(20):
75
            try:
76
                getattr(self, 'provision_' + object_type)(issuer, action, notification['objects']['data'], full=full)
77
            except TryAgain:
78
                continue
79
            break
80

  
81
        return JsonResponse({'err': 0})
hobo/provisionning/utils.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import random
18
import logging
19

  
20
from django.db.transaction import atomic
21
from django.db import IntegrityError
22

  
23
from hobo.multitenant.utils import provision_user_groups
24
from hobo.agent.common.models import Role
25

  
26

  
27
class TryAgain(Exception):
28
    pass
29

  
30

  
31
class NotificationProcessing:
32

  
33
    @classmethod
34
    def check_valid_notification(cls, notification):
35
        return isinstance(notification, dict) \
36
            and '@type' in notification \
37
            and notification['@type'] in ['provision', 'deprovision'] \
38
            and 'objects' in notification \
39
            and 'audience' in notification \
40
            and isinstance(notification['audience'], list) \
41
            and isinstance(notification['objects'], dict)
42

  
43
    @classmethod
44
    def check_valid_role(cls, o):
45
        return 'uuid' in o \
46
               and 'name' in o \
47
               and 'description' in o
48

  
49
    @classmethod
50
    def check_valid_user(cls, o):
51
        return 'uuid' in o \
52
               and 'is_superuser' in o \
53
               and 'email' in o \
54
               and 'first_name' in o \
55
               and 'last_name' in o \
56
               and 'roles' in o
57

  
58
    @classmethod
59
    def provision_user(cls, issuer, action, data, full=False):
60
        from django.contrib.auth import get_user_model
61
        from mellon.models import UserSAMLIdentifier
62
        User = get_user_model()
63

  
64
        assert not full  # provisionning all users is dangerous, we prefer deprovision
65
        uuids = set()
66
        for o in data:
67
            try:
68
                with atomic():
69
                    if action == 'provision':
70
                        assert cls.check_valid_user(o)
71
                        try:
72
                            mellon_user = UserSAMLIdentifier.objects.get(
73
                                issuer=issuer, name_id=o['uuid'])
74
                            user = mellon_user.user
75
                        except UserSAMLIdentifier.DoesNotExist:
76
                            try:
77
                                user = User.objects.get(username=o['uuid'][:30])
78
                            except User.DoesNotExist:
79
                                # temp user object
80
                                random_uid = str(random.randint(1, 10000000000000))
81
                                user = User.objects.create(
82
                                    username=random_uid)
83
                            mellon_user = UserSAMLIdentifier.objects.create(
84
                                user=user, issuer=issuer, name_id=o['uuid'])
85
                        user.first_name = o['first_name'][:30]
86
                        user.last_name = o['last_name'][:30]
87
                        user.email = o['email'][:75]
88
                        user.username = o['uuid'][:30]
89
                        user.is_superuser = o['is_superuser']
90
                        user.is_staff = o['is_superuser']
91
                        user.save()
92
                        role_uuids = [role['uuid'] for role in o.get('roles', [])]
93
                        provision_user_groups(user, role_uuids)
94
                    elif action == 'deprovision':
95
                        assert 'uuid' in o
96
                uuids.add(o['uuid'])
97
            except IntegrityError:
98
                raise TryAgain
99
        if full and action == 'provision':
100
            for usi in UserSAMLIdentifier.objects.exclude(name_id__in=uuids):
101
                usi.user.delete()
102
        elif action == 'deprovision':
103
            for user in User.objects.filter(saml_identifiers__name_id__in=uuids):
104
                user.delete()
105

  
106
    @classmethod
107
    def provision_role(cls, issuer, action, data, full=False):
108
        logger = logging.getLogger(__name__)
109
        uuids = set()
110
        for o in data:
111
            assert 'uuid' in o
112
            uuids.add(o['uuid'])
113
            if action == 'provision':
114
                assert cls.check_valid_role(o)
115
                role_name = o['name']
116
                if len(role_name) > 70:
117
                    role_name = role_name[:70] + '(...)'
118
                try:
119
                    role = Role.objects.get(uuid=o['uuid'])
120
                    created = False
121
                except Role.DoesNotExist:
122
                    try:
123
                        with atomic():
124
                            role, created = Role.objects.get_or_create(
125
                                name=role_name, defaults={
126
                                    'uuid': o['uuid'],
127
                                    'description': o['description']})
128
                    except IntegrityError:
129
                        # Can happen if uuid and name already exist
130
                        logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
131
                        continue
132
                if not created:
133
                    save = False
134
                    if role.name != role_name:
135
                        role.name = role_name
136
                        save = True
137
                    if role.uuid != o['uuid']:
138
                        role.uuid = o['uuid']
139
                        save = True
140
                    if role.description != o['description']:
141
                        role.description = o['description']
142
                        save = True
143
                    if role.details != o.get('details', u''):
144
                        role.details = o.get('details', u'')
145
                        save = True
146
                    if save:
147
                        try:
148
                            with atomic():
149
                                role.save()
150
                        except IntegrityError:
151
                            # Can happen if uuid and name already exist
152
                            logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
153
                            continue
154
        if full and action == 'provision':
155
            for role in Role.objects.exclude(uuid__in=uuids):
156
                role.delete()
157
        elif action == 'deprovision':
158
            for role in Role.objects.filter(uuid__in=uuids):
159
                role.delete()
tests/settings.py
2 2
BROKER_URL = 'memory://'
3 3
OZWILLO_SECRET = 'secret'
4 4

  
5
INSTALLED_APPS += ('hobo.contrib.ozwillo',)
5
INSTALLED_APPS += ('hobo.contrib.ozwillo', 'hobo.agent.common')
6 6

  
7 7
ALLOWED_HOSTS.append('localhost')
8 8

  
9 9
TEMPLATES[0]['OPTIONS']['debug'] = True
10 10

  
11
MIDDLEWARE_CLASSES = ('hobo.middleware.RobotsTxtMiddleware', ) + MIDDLEWARE_CLASSES
11
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
12
        'hobo.middleware.RobotsTxtMiddleware',
13
        'hobo.provisionning.middleware.ProvisionningMiddleware')
12 14

  
13 15
HOBO_MANAGER_HOMEPAGE_URL_VAR = 'portal_agent_url'
tests_authentic/test_provisionning.py
535 535
    assert notify_agents.call_count == 0
536 536
    resp = resp.form.submit().follow()
537 537
    assert notify_agents.call_count == 1
538

  
539

  
540
def test_provision_using_http(transactional_db, tenant, settings, caplog):
541
    with tenant_context(tenant):
542
        # create providers so notification messages have an audience.
543
        LibertyProvider.objects.create(ou=None, name='provider', slug='provider',
544
                                       entity_id='http://example.org',
545
                                       protocol_conformance=lasso.PROTOCOL_SAML_2_0)
546
        LibertyProvider.objects.create(ou=None, name='provider2', slug='provider2',
547
                                       entity_id='http://example.com',
548
                                       protocol_conformance=lasso.PROTOCOL_SAML_2_0)
549
    with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
550
        call_command('createsuperuser', domain=tenant.domain_url, uuid='coin',
551
                     username='coin', email='coin@coin.org', interactive=False)
552
        assert notify_agents.call_count == 1
553
        assert set(notify_agents.call_args[0][0]['audience']) == {'http://example.org', 'http://example.com'}
554

  
555
    settings.HOBO_HTTP_PROVISIONNING = True
556
    with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
557
        call_command('createsuperuser', domain=tenant.domain_url, uuid='coin2',
558
                     username='coin2', email='coin2@coin.org', interactive=False)
559
        assert notify_agents.call_count == 1
560
        assert set(notify_agents.call_args[0][0]['audience']) == {'http://example.org', 'http://example.com'}
561

  
562
    settings.HOBO_HTTP_PROVISIONNING = True
563
    settings.KNOWN_SERVICES = {
564
        'foo': {
565
            'bar': {
566
                'saml-sp-metadata-url': 'http://example.org',
567
                'provisionning-url': 'http://example.org/__provision__/',
568
                'orig': 'example.org',
569
                'secret': 'xxx',
570
            }
571
        }
572
    }
573
    with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
574
        with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
575
            call_command('createsuperuser', domain=tenant.domain_url, uuid='coin2',
576
                         username='coin2', email='coin2@coin.org', interactive=False)
577
            assert notify_agents.call_count == 1
578
            assert notify_agents.call_args[0][0]['audience'] == ['http://example.com']
579
            assert requests_put.call_count == 1
580
            # cannot check audience passed to requests.put as it's the same
581
            # dictionary that is altered afterwards and would thus also contain
582
            # http://example.com.
583

  
584
    settings.KNOWN_SERVICES['foo']['bar2'] = {
585
        'saml-sp-metadata-url': 'http://example.com',
586
        'provisionning-url': 'http://example.com/__provision__/',
587
        'orig': 'example.com',
588
        'secret': 'xxx',
589
    }
590
    with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
591
        with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
592
            call_command('createsuperuser', domain=tenant.domain_url, uuid='coin2',
593
                         username='coin2', email='coin2@coin.org', interactive=False)
594
            assert notify_agents.call_count == 0
595
            assert requests_put.call_count == 2
tests_multitenant/test_settings.py
245 245
            assert 'other' in settings.KNOWN_SERVICES['authentic']
246 246
            assert (set(['url', 'backoffice-menu-url', 'title', 'orig',
247 247
                         'verif_orig', 'secret', 'template_name', 'variables',
248
                         'secondary'])
248
                         'saml-sp-metadata-url', 'provisionning-url', 'secondary'])
249 249
                    == set(settings.KNOWN_SERVICES['authentic']['other'].keys()))
250 250
            assert (settings.KNOWN_SERVICES['authentic']['other']['url']
251 251
                    == hobo_json['services'][2]['base_url'])
tests_schemas/example_env.json
139 139
        {
140 140
            "backoffice-menu-url": "https://hobo-instance-name.dev.signalpublik.com/menu.json",
141 141
            "base_url": "https://hobo-instance-name.dev.signalpublik.com/",
142
            "provisionning-url": "https://hobo-instance-name.dev.signalpublik.com/__provision__/",
142 143
            "saml-sp-metadata-url": "https://hobo-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
144
            "secret_key": "XXX",
143 145
            "service-id": "hobo",
144 146
            "slug": "hobo",
145 147
            "title": "Hobo"
......
150 152
            "id": 1,
151 153
            "saml-idp-metadata-url": "https://connexion-instance-name.dev.signalpublik.com/idp/saml2/metadata",
152 154
            "secondary": false,
153
            "secret_key": "k_a)vo)a&8xugbzjl#%^s8vfkm2+#yhz#if4m+xu!qqv=04x9q",
155
            "secret_key": "XXX",
154 156
            "service-id": "authentic",
155 157
            "service-label": "Authentic",
156 158
            "slug": "idp",
157 159
            "template_name": "signal-publik",
158 160
            "title": "Connexion",
159
            "variables": {},
160
            "use_as_idp_for_self": true
161
            "use_as_idp_for_self": true,
162
            "variables": {}
161 163
        },
162 164
        {
163 165
            "backoffice-menu-url": "https://demarches-instance-name.dev.signalpublik.com/backoffice/menu.json",
164 166
            "base_url": "https://demarches-instance-name.dev.signalpublik.com/",
165 167
            "id": 1,
168
            "provisionning-url": "https://demarches-instance-name.dev.signalpublik.com/__provision__/",
166 169
            "saml-sp-metadata-url": "https://demarches-instance-name.dev.signalpublik.com/saml/metadata",
167 170
            "secondary": false,
168
            "secret_key": "uhipz^y38a*w#rrnio_-i=+7p47aq#$+dntm*i@nz(y)n57153",
171
            "secret_key": "XXX",
169 172
            "service-id": "wcs",
170 173
            "service-label": "w.c.s.",
171 174
            "slug": "eservices",
......
177 180
            "backoffice-menu-url": "https://passerelle-instance-name.dev.signalpublik.com/manage/menu.json",
178 181
            "base_url": "https://passerelle-instance-name.dev.signalpublik.com/",
179 182
            "id": 1,
183
            "provisionning-url": "https://passerelle-instance-name.dev.signalpublik.com/__provision__/",
180 184
            "saml-sp-metadata-url": "https://passerelle-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
181 185
            "secondary": false,
182
            "secret_key": "vz&g(p1bhzw35iltrrl$^6013*+q80l&l4)b)tsr=+ko__js_v",
186
            "secret_key": "XXX",
183 187
            "service-id": "passerelle",
184 188
            "service-label": "Passerelle",
185 189
            "slug": "passerelle",
......
191 195
            "backoffice-menu-url": "https://instance-name.dev.signalpublik.com/manage/menu.json",
192 196
            "base_url": "https://instance-name.dev.signalpublik.com/",
193 197
            "id": 1,
198
            "provisionning-url": "https://instance-name.dev.signalpublik.com/__provision__/",
194 199
            "saml-sp-metadata-url": "https://instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
195 200
            "secondary": false,
196
            "secret_key": "^0!psa-ijq4*va0a4&_)solvils#hig2vtof(%3iy#!6p5!f6e",
201
            "secret_key": "XXX",
197 202
            "service-id": "combo",
198 203
            "service-label": "Combo",
199 204
            "slug": "portal",
......
205 210
            "backoffice-menu-url": "https://agents-instance-name.dev.signalpublik.com/manage/menu.json",
206 211
            "base_url": "https://agents-instance-name.dev.signalpublik.com/",
207 212
            "id": 2,
213
            "provisionning-url": "https://agents-instance-name.dev.signalpublik.com/__provision__/",
208 214
            "saml-sp-metadata-url": "https://agents-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
209 215
            "secondary": false,
210
            "secret_key": "m1&vql=pm-clw)0wcnk=q4g1-#flrus!dui$gr$7ug2%xw@ko$",
216
            "secret_key": "XXX",
211 217
            "service-id": "combo",
212 218
            "service-label": "Combo",
213 219
            "slug": "portal-agent",
......
216 222
            "variables": {}
217 223
        }
218 224
    ],
219
    "timestamp": "1558975192.98",
225
    "timestamp": "XXXXXXXXXX.XX",
220 226
    "users": [],
221 227
    "variables": {
222 228
        "css_variant": "publik",
223
-