Projet

Général

Profil

0001-manager-add-OpenID-service-handling-20696.patch

Voir les différences:

Subject: [PATCH] manager: add OpenID service handling (#20696)

 src/authentic2/manager/service_views.py       | 31 ++++++-
 .../static/authentic2/manager/css/style.scss  | 23 +++++
 .../templates/authentic2/manager/service.html | 16 +++-
 .../authentic2/manager/services.html          |  9 ++
 src/authentic2/manager/urls.py                | 37 ++++----
 src/authentic2/manager/utils.py               | 17 ++++
 src/authentic2/models.py                      |  5 ++
 src/authentic2_idp_oidc/manager/__init__.py   |  0
 src/authentic2_idp_oidc/manager/forms.py      | 64 +++++++++++++
 src/authentic2_idp_oidc/manager/urls.py       | 45 ++++++++++
 src/authentic2_idp_oidc/manager/views.py      | 87 ++++++++++++++++++
 src/authentic2_idp_oidc/models.py             | 28 ++++++
 .../manager/object_detail.html                | 31 +++++++
 tests/test_manager.py                         | 89 +++++++++++++++++++
 14 files changed, 455 insertions(+), 27 deletions(-)
 create mode 100644 src/authentic2_idp_oidc/manager/__init__.py
 create mode 100644 src/authentic2_idp_oidc/manager/forms.py
 create mode 100644 src/authentic2_idp_oidc/manager/urls.py
 create mode 100644 src/authentic2_idp_oidc/manager/views.py
 create mode 100644 src/authentic2_idp_oidc/templates/authentic2_idp_oidc/manager/object_detail.html
src/authentic2/manager/service_views.py
39 39
listing = ServicesView.as_view()
40 40

  
41 41

  
42
class ServiceMixin:
43
    def get_object(self, queryset=None):
44
        service = super().get_object(queryset)
45
        if hasattr(service, 'oidcclient'):
46
            return service.oidcclient
47
        return service
48

  
49

  
42 50
class ServiceView(
51
    ServiceMixin,
43 52
    views.SimpleSubTableView,
44 53
    role_views.RoleViewMixin,
45 54
    views.MediaMixin,
......
86 95
        kwargs['form'] = self.get_form()
87 96
        ctx = super().get_context_data(**kwargs)
88 97
        ctx['roles_table'] = tables.RoleTable(self.object.roles.all())
98
        ctx.update(self.object.get_manager_context_data())
89 99
        return ctx
90 100

  
91 101

  
92
roles = ServiceView.as_view()
102
service_detail = ServiceView.as_view()
93 103

  
94 104

  
95
class ServiceEditView(views.BaseEditView):
105
class ServiceEditView(ServiceMixin, views.BaseEditView):
96 106
    model = Service
97 107
    pk_url_kwarg = 'service_pk'
98 108
    template_name = 'authentic2/manager/form.html'
......
101 111
    fields = ['name', 'slug', 'ou', 'unauthorized_url']
102 112
    success_url = '..'
103 113

  
114
    def get_form_class(self):
115
        if self.object.manager_form_class:
116
            return self.object.manager_form_class
117
        return super().get_form_class()
118

  
119

  
120
edit_service = ServiceEditView.as_view()
121

  
122

  
123
class ServiceDeleteView(views.BaseDeleteView):
124
    model = Service
125
    pk_url_kwarg = 'service_pk'
126
    permissions = ['authentic2.delete_service']
127
    title = _('Delete OpenID Service')
128

  
104 129

  
105
edit = ServiceEditView.as_view()
130
delete_service = ServiceDeleteView.as_view()
src/authentic2/manager/static/authentic2/manager/css/style.scss
287 287
	max-width: 100%;
288 288
	overflow-y: auto;
289 289
}
290

  
291
table.claims-table td.actions {
292
	position: relative;
293
	width: 80px;
294
	a {
295
		display: inline-block;
296
		border: none;
297
		overflow: hidden;
298
		width: 30px;
299
		height: 30px;
300
		line-height: 30px;
301
		&::before {
302
			font-family: FontAwesome;
303
			padding-right: 3em;
304
		}
305
		&.delete::before {
306
			content: "\f057"; /* remove-sign */
307
		}
308
		&.edit::before {
309
			content: "\f044"; /* edit-sign */
310
		}
311
	}
312
}
src/authentic2/manager/templates/authentic2/manager/service.html
1
{% extends "authentic2/manager/services.html" %}
1
{% extends "authentic2/manager/base.html" %}
2 2
{% load i18n static django_tables2 %}
3 3

  
4 4
{% block page-title %}{% firstof manager_site_title site_title "Authentic2" %} - {{ object }}{% endblock %}
5 5

  
6 6
{% block breadcrumb %}
7
  {{ block.super }}
7
{{ block.super }}
8
  <a href="{% url 'a2-manager-services' %}">{% trans 'Services' %}</a>
8 9
  <a href="{% url 'a2-manager-service' service_pk=view.kwargs.service_pk %}">{{ view.service.name }}</a>
9 10
{% endblock %}
10 11

  
12
{% block buttons %}
13
{% endblock %}
14

  
11 15
{% block appbar %}
12 16
  {{ block.super }}
13 17
  <span class="actions">
18
  {% if view.can_delete %}
19
  <a rel="popup" href="{% url "a2-manager-service-delete" service_pk=view.kwargs.service_pk %}">{% trans "Delete" %}</a>
20
  {% endif %}
14 21
  {% if view.can_change %}
15 22
  <a rel="popup" href="{% url "a2-manager-service-edit" service_pk=view.kwargs.service_pk %}">{% trans "Edit" %}</a>
16 23
  {% endif %}
......
34 41
{% endblock %}
35 42

  
36 43
{% block main %}
44

  
45
{% if extra_details_template %}
46
  {% include extra_details_template %}
47
{% endif %}
48

  
37 49
<div class="section">
38 50
  <h3>{% trans "Roles of users allowed on this service" %}</h3>
39 51
  <div id="authorized-roles">
src/authentic2/manager/templates/authentic2/manager/services.html
8 8
  <a href="{% url 'a2-manager-services' %}">{% trans 'Services' %}</a>
9 9
{% endblock %}
10 10

  
11
{% block appbar %}
12
  {{ block.super }}
13
  <span class="actions">
14
    {% if view.can_add %}
15
    <a href="{% url "a2-manager-add-oidc-service" %}">{% trans "Add OIDC service" %}</a>
16
    {% endif %}
17
  </span>
18
{% endblock %}
19

  
11 20
{% block sidebar %}
12 21
  <aside id="sidebar">
13 22
    {% include "authentic2/manager/search_form.html" %}
src/authentic2/manager/urls.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
from functools import wraps
18

  
19 17
from django.conf.urls import url
20
from django.utils.functional import lazy
21 18
from django.views.i18n import JavaScriptCatalog
22 19

  
23 20
from authentic2.apps.authenticators.manager_urls import urlpatterns as authenticator_urlpatterns
24
from authentic2.utils import misc as utils_misc
21
from authentic2_idp_oidc.manager.urls import urlpatterns as oidc_manager_urlpatterns
25 22

  
26 23
from ..decorators import required
27
from . import journal_views, ou_views, role_views, service_views, user_views, views
28

  
29

  
30
def manager_login_required(func):
31
    @wraps(func)
32
    def _wrapped_view(request, *args, **kwargs):
33
        if request.user.is_authenticated:
34
            return func(request, *args, **kwargs)
35
        return utils_misc.login_require(
36
            request, login_url=lazy(utils_misc.get_manager_login_url, str)(), login_hint=['backoffice']
37
        )
38

  
39
    return _wrapped_view
40

  
24
from . import journal_views, ou_views, role_views, service_views, user_views, utils, views
41 25

  
42 26
urlpatterns = required(
43
    manager_login_required,
27
    utils.manager_login_required,
44 28
    [
45 29
        # homepage
46 30
        url(r'^$', views.homepage, name='a2-manager-homepage'),
......
182 166
        url(r'^organizational-units/import/$', ou_views.ous_import, name='a2-manager-ous-import'),
183 167
        # Services
184 168
        url(r'^services/$', service_views.listing, name='a2-manager-services'),
185
        url(r'^services/(?P<service_pk>\d+)/$', service_views.roles, name='a2-manager-service'),
186
        url(r'^services/(?P<service_pk>\d+)/edit/$', service_views.edit, name='a2-manager-service-edit'),
187
        # Journal
169
        url(r'^services/(?P<service_pk>\d+)/$', service_views.service_detail, name='a2-manager-service'),
170
        url(
171
            r'^services/(?P<service_pk>\d+)/edit/$',
172
            service_views.edit_service,
173
            name='a2-manager-service-edit',
174
        ),
175
        url(
176
            r'^services/(?P<service_pk>\d+)/delete/$',
177
            service_views.delete_service,
178
            name='a2-manager-service-delete',
179
        ),  # Journal
188 180
        url(r'^journal/$', journal_views.journal, name='a2-manager-journal'),
189 181
        url(
190 182
            r'^journal/event-types/$',
......
202 194
)
203 195

  
204 196
urlpatterns += authenticator_urlpatterns
197
urlpatterns += oidc_manager_urlpatterns
205 198

  
206 199
urlpatterns += [
207 200
    url(
src/authentic2/manager/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
from functools import wraps
18

  
19
from django.utils.functional import lazy
20

  
17 21
from authentic2.a2_rbac.models import OrganizationalUnit
22
from authentic2.utils import misc as utils_misc
18 23
from authentic2.utils.cache import GlobalCache
19 24

  
20 25

  
......
44 49
@GlobalCache(timeout=10)
45 50
def has_show_username():
46 51
    return not OrganizationalUnit.objects.filter(show_username=False).exists()
52

  
53

  
54
def manager_login_required(func):
55
    @wraps(func)
56
    def _wrapped_view(request, *args, **kwargs):
57
        if request.user.is_authenticated:
58
            return func(request, *args, **kwargs)
59
        return utils_misc.login_require(
60
            request, login_url=lazy(utils_misc.get_manager_login_url, str)(), login_hint=['backoffice']
61
        )
62

  
63
    return _wrapped_view
src/authentic2/models.py
416 416

  
417 417
    objects = managers.ServiceManager()
418 418

  
419
    manager_form_class = None
420

  
419 421
    def clean(self):
420 422
        errors = {}
421 423

  
......
499 501

  
500 502
        return super().delete(*args, **kwargs)
501 503

  
504
    def get_manager_context_data(self):
505
        return {}
506

  
502 507

  
503 508
Service._meta.natural_key = [['slug', 'ou']]
504 509

  
src/authentic2_idp_oidc/manager/forms.py
1
# authentic2 - versatile identity manager
2
# Copyright (C) 2022 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
from django import forms
18

  
19
from authentic2.attributes_ng.engine import get_service_attributes
20
from authentic2.forms.mixins import SlugMixin
21
from authentic2.forms.widgets import DatalistTextInput
22
from authentic2_idp_oidc.models import OIDCClaim, OIDCClient
23

  
24

  
25
class OIDCClientForm(SlugMixin, forms.ModelForm):
26
    class Meta:
27
        model = OIDCClient
28
        fields = [
29
            'name',
30
            'redirect_uris',
31
            'post_logout_redirect_uris',
32
            'sector_identifier_uri',
33
            'frontchannel_logout_uri',
34
            'ou',
35
            'identifier_policy',
36
            'idtoken_algo',
37
            'unauthorized_url',
38
            'authorization_mode',
39
            'authorization_flow',
40
            'home_url',
41
            'colour',
42
            'logo',
43
        ]
44

  
45
    def __init__(self, *args, **kwargs):
46
        super().__init__(*args, **kwargs)
47
        self.fields['colour'].widget = forms.TextInput(attrs={'type': 'color'})
48

  
49

  
50
class OIDCClaimForm(forms.ModelForm):
51
    class Meta:
52
        model = OIDCClaim
53
        fields = ('name', 'value', 'scopes')
54
        widgets = {
55
            'value': DatalistTextInput,
56
        }
57

  
58
    def __init__(self, *args, **kwargs):
59
        super().__init__(*args, **kwargs)
60
        data = dict(get_service_attributes(getattr(self.instance, 'client', None))).keys()
61
        widget = self.fields['value'].widget
62
        widget.data = data
63
        widget.name = 'list__oidcclaim-inline'
64
        widget.attrs.update({'list': 'list__oidcclaim-inline'})
src/authentic2_idp_oidc/manager/urls.py
1
# authentic2 - versatile identity manager
2
# Copyright (C) 2022p 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

  
18
from django.conf.urls import url
19

  
20
from authentic2.decorators import required
21
from authentic2.manager.utils import manager_login_required
22

  
23
from . import views
24

  
25
urlpatterns = required(
26
    manager_login_required,
27
    [
28
        url(r'^services/add-oidc/$', views.add_oidc_service, name='a2-manager-add-oidc-service'),
29
        url(
30
            r'^services/(?P<service_pk>\d+)/claim/add/$',
31
            views.oidc_claim_add,
32
            name='a2-manager-oidc-claim-add',
33
        ),
34
        url(
35
            r'^services/(?P<service_pk>\d+)/claim/(?P<claim_pk>\d+)/edit/$',
36
            views.oidc_claim_edit,
37
            name='a2-manager-oidc-claim-edit',
38
        ),
39
        url(
40
            r'^services/(?P<service_pk>\d+)/claim/(?P<claim_pk>\d+)/delete/$',
41
            views.oidc_claim_delete,
42
            name='a2-manager-oidc-claim-delete',
43
        ),
44
    ],
45
)
src/authentic2_idp_oidc/manager/views.py
1
# authentic2 - versatile identity manager
2
# Copyright (C) 2022 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
from django.urls import reverse
18
from django.utils.translation import ugettext_lazy as _
19

  
20
from authentic2.manager import views
21
from authentic2_idp_oidc import app_settings
22
from authentic2_idp_oidc.models import OIDCClaim, OIDCClient
23

  
24
from . import forms
25

  
26

  
27
class OIDCServiceAddView(views.ActionMixin, views.BaseAddView):
28
    form_class = forms.OIDCClientForm
29
    model = OIDCClient
30
    title = _('Add OIDC service')
31
    permissions = ['authentic2.add_service']
32
    action = _('Add')
33

  
34
    def get_success_url(self):
35
        # add default claims mappings after creating service
36
        for mapping in app_settings.DEFAULT_MAPPINGS:
37
            OIDCClaim.objects.get_or_create(client=self.object, **mapping)
38
        return reverse('a2-manager-service', kwargs={'service_pk': self.object.pk})
39

  
40

  
41
add_oidc_service = OIDCServiceAddView.as_view()
42

  
43

  
44
class OIDCClaimAddView(views.ActionMixin, views.BaseAddView):
45
    form_class = forms.OIDCClaimForm
46
    model = OIDCClaim
47
    title = _('Add OIDC Claim')
48
    action = _('Add')
49

  
50
    def form_valid(self, form):
51
        obj = form.save(commit=False)
52
        obj.client = OIDCClient.objects.get(pk=self.kwargs['service_pk'])
53
        obj.save()
54
        return super().form_valid(form)
55

  
56
    def get_success_url(self):
57
        return reverse('a2-manager-service', kwargs={'service_pk': self.object.client.pk})
58

  
59

  
60
oidc_claim_add = OIDCClaimAddView.as_view()
61

  
62

  
63
class BaseClaimView:
64
    model = OIDCClaim
65
    pk_url_kwarg = 'claim_pk'
66

  
67
    def get_queryset(self):
68
        qs = super().get_queryset()
69
        return qs.filter(client__pk=self.kwargs['service_pk'])
70

  
71
    def get_success_url(self):
72
        return reverse('a2-manager-service', kwargs={'service_pk': self.object.client.pk})
73

  
74

  
75
class OIDCClaimEditView(BaseClaimView, views.BaseEditView):
76
    title = _('Edit OpenID Claim')
77
    form_class = forms.OIDCClaimForm
78

  
79

  
80
oidc_claim_edit = OIDCClaimEditView.as_view()
81

  
82

  
83
class OIDCClaimDeleteView(BaseClaimView, views.BaseDeleteView):
84
    title = _('Delete OpenID Claim')
85

  
86

  
87
oidc_claim_delete = OIDCClaimDeleteView.as_view()
src/authentic2_idp_oidc/models.py
238 238
            self.get_identifier_policy_display(),
239 239
        )
240 240

  
241
    @property
242
    def manager_form_class(self):
243
        from .manager.forms import OIDCClientForm
244

  
245
        return OIDCClientForm
246

  
247
    def get_manager_fields(self):
248
        # add client id and secret
249
        display_fields = ['client_id', 'client_secret'] + self.manager_form_class._meta.fields
250
        # but remove name because it's already displayed
251
        if 'name' in display_fields:
252
            display_fields.remove('name')
253
        for field in display_fields:
254
            field_value = getattr(self, field)
255
            if not field_value:
256
                continue
257
            if hasattr(self, 'get_%s_display' % field):
258
                field_value = getattr(self, 'get_%s_display' % field)()
259
            yield self._meta.get_field(field).verbose_name, field_value
260

  
261
    def get_manager_context_data(self):
262
        ctx = {
263
            'claims': self.oidcclaim_set.all(),
264
            'object_fields': self.get_manager_fields(),
265
            'extra_details_template': 'authentic2_idp_oidc/manager/object_detail.html',
266
        }
267
        return ctx
268

  
241 269

  
242 270
class OIDCAuthorization(models.Model):
243 271
    client_ct = models.ForeignKey(
src/authentic2_idp_oidc/templates/authentic2_idp_oidc/manager/object_detail.html
1
{% load i18n %}
2
{% for field, value in object_fields %}
3
  <p>{{ field|capfirst }}{% trans ":" %}  {% if value == True %}{% trans "yes" %}
4
  {% elif value == False %}{% trans "no" %}
5
  {% else %}{{value}}
6
  {% endif %}</p>
7
{% endfor %}
8

  
9
  <div class="section">
10
  <h3>{% trans "OIDC Claims" %}<a href="{% url "a2-manager-oidc-claim-add" service_pk=object.pk %}" class="button" rel="popup">{% trans "Add claim" %}</a></h3>
11
  {% if claims %}
12
  <table class="main claims-table" id="oidc-claims">
13
    <thead>
14
      <tr><th>{% trans "Name" %}</th><th>{% trans "Value" %}</th><th>{% trans "Scopes" %}</th><th></th></tr>
15
    </thead>
16
    <tbody>
17
    {% for claim in claims %}
18
    <tr>
19
      <td>{{ claim.name }}</td>
20
      <td>{{ claim.value }}</td>
21
      <td>{{ claim.scopes }}</td>
22
      <td class="actions">
23
        <a class="edit" href="{% url "a2-manager-oidc-claim-edit" service_pk=object.pk claim_pk=claim.pk %}" rel="popup" title="{% trans "Edit" %}">{% trans "Edit" %}</a>
24
        <a class="delete" href="{% url "a2-manager-oidc-claim-delete" service_pk=object.pk claim_pk=claim.pk %}" rel="popup" title="{% trans "Delete" %}">{% trans "Delete" %}</a>
25
      </td>
26
    </tr>
27
    {% endfor %}
28
    </tbody>
29
  </table>
30
  {% endif %}
31
</div>
tests/test_manager.py
34 34
from authentic2.apps.journal.models import Event
35 35
from authentic2.models import Service
36 36
from authentic2.validators import EmailValidator
37
from authentic2_idp_oidc import app_settings as oidc_app_settings
38
from authentic2_idp_oidc.models import OIDCClaim, OIDCClient
37 39
from django_rbac.models import VIEW_OP
38 40
from django_rbac.utils import get_operation
39 41

  
......
1220 1222
    resp = resp.form.submit()
1221 1223
    assert 'Test Service' in resp.text
1222 1224
    assert 'Example Service' not in resp.text
1225

  
1226

  
1227
def test_manager_add_oidc_service(app, superuser):
1228
    resp = login(app, superuser, 'a2-manager-services')
1229
    assert 'Add OIDC service' in resp.text
1230
    assert OIDCClient.objects.count() == 0
1231
    assert OIDCClaim.objects.count() == 0
1232

  
1233
    resp = resp.click('Add OIDC service')
1234
    form = resp.form
1235
    form['name'] = 'Test'
1236
    form['redirect_uris'] = 'http://example.com'
1237
    resp = form.submit()
1238

  
1239
    assert OIDCClient.objects.count() == 1
1240
    assert OIDCClaim.objects.count() == len(oidc_app_settings.DEFAULT_MAPPINGS)
1241
    assert resp.location == reverse('a2-manager-service', kwargs={'service_pk': OIDCClient.objects.get().pk})
1242

  
1243
    resp = resp.follow()
1244
    assert "<h3>OIDC Claims" in resp.text
1245
    assert "Add claim" in resp.text
1246
    assert resp.pyquery.remove_namespaces()('#oidc-claims tbody tr').length == len(
1247
        oidc_app_settings.DEFAULT_MAPPINGS
1248
    )
1249

  
1250

  
1251
def test_manager_edit_oidc_service(app, superuser):
1252
    OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')
1253
    resp = login(app, superuser, 'a2-manager-services')
1254
    resp = resp.click('Test')
1255
    resp = resp.click('Edit')
1256
    form = resp.form
1257
    form['name'] = 'New Test'
1258
    form['colour'] = '#ff00ff'
1259
    resp = form.submit()
1260
    assert resp.location == '..'
1261
    resp = resp.follow()
1262
    assert "New Test" in resp.text
1263
    assert "#ff00ff" in resp.text
1264

  
1265

  
1266
def test_manager_delete_oidc_service(app, superuser):
1267
    OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')
1268
    resp = login(app, superuser, 'a2-manager-services')
1269
    resp = resp.click('Test')
1270
    resp = resp.click('Delete')
1271
    resp = resp.form.submit().follow()
1272
    assert OIDCClient.objects.count() == 0
1273

  
1274

  
1275
def test_manager_add_oidc_claim(app, superuser):
1276
    client = OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')
1277
    resp = login(app, superuser, reverse('a2-manager-service', kwargs={'service_pk': client.pk}))
1278
    resp = resp.click('Add claim')
1279
    form = resp.form
1280
    form['name'] = 'claim'
1281
    form['value'] = 'value'
1282
    form['scopes'] = 'profile'
1283
    resp = form.submit()
1284
    assert resp.location == reverse('a2-manager-service', kwargs={'service_pk': client.pk})
1285
    assert OIDCClaim.objects.filter(client=client, name='claim', value='value', scopes='profile').exists()
1286

  
1287

  
1288
def test_manager_edit_oidc_claim(app, superuser):
1289
    client = OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')
1290
    OIDCClaim.objects.create(client=client, name='claim', value='value', scopes='profile')
1291
    resp = login(app, superuser, reverse('a2-manager-service', kwargs={'service_pk': client.pk}))
1292
    assert "claim" in resp.text
1293
    resp = resp.click('Edit', index=1)
1294
    form = resp.form
1295
    form['value'] = 'new value'
1296
    resp = form.submit()
1297
    assert resp.location == reverse('a2-manager-service', kwargs={'service_pk': client.pk})
1298
    assert not OIDCClaim.objects.filter(client=client, name='claim', value='value', scopes='profile').exists()
1299
    assert OIDCClaim.objects.filter(client=client, name='claim', value='new value', scopes='profile').exists()
1300

  
1301

  
1302
def test_manager_delete_oidc_claim(app, superuser):
1303
    client = OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')
1304
    OIDCClaim.objects.create(client=client, name='claim', value='value', scopes='profile')
1305
    resp = login(app, superuser, reverse('a2-manager-service', kwargs={'service_pk': client.pk}))
1306
    assert "claim" in resp.text
1307
    resp = resp.click('Delete', index=1)
1308
    form = resp.form
1309
    resp = form.submit()
1310
    assert resp.location == reverse('a2-manager-service', kwargs={'service_pk': client.pk})
1311
    assert not OIDCClaim.objects.filter(client=client, name='claim', value='value', scopes='profile').exists()
1223
-