manager: start api client interface (#68404)
This commit is contained in:
parent
2be6ad6859
commit
e6ff6db061
|
@ -35,7 +35,7 @@ from authentic2.a2_rbac.utils import generate_slug, get_default_ou
|
|||
from authentic2.forms.fields import CheckPasswordField, NewPasswordField, ValidatedEmailField
|
||||
from authentic2.forms.mixins import SlugMixin
|
||||
from authentic2.forms.profile import BaseUserForm
|
||||
from authentic2.models import PasswordReset
|
||||
from authentic2.models import APIClient, PasswordReset
|
||||
from authentic2.passwords import generate_password
|
||||
from authentic2.utils.misc import (
|
||||
import_module_or_class,
|
||||
|
@ -49,6 +49,7 @@ from django_rbac.models import Operation
|
|||
from . import app_settings, fields, utils
|
||||
|
||||
User = get_user_model()
|
||||
ChooseRolesField = fields.ChooseRolesField
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -897,3 +898,26 @@ class ChooseUserOrRoleForm(FormWithRequest, forms.Form):
|
|||
|
||||
perm = '%s.search_%s' % (User._meta.app_label, User._meta.model_name)
|
||||
return user.filter_by_perm(perm, qs)
|
||||
|
||||
|
||||
class APIClientForm(forms.ModelForm):
|
||||
field_order = (
|
||||
'name',
|
||||
'description',
|
||||
'identifier',
|
||||
'password',
|
||||
'restrict_to_anonymised_data',
|
||||
'apiclient_roles',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = APIClient
|
||||
fields = (
|
||||
'name',
|
||||
'description',
|
||||
'identifier',
|
||||
'password',
|
||||
'restrict_to_anonymised_data',
|
||||
'apiclient_roles',
|
||||
)
|
||||
field_classes = {'apiclient_roles': ChooseRolesField}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "authentic2/manager/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page-title %}{{ block.super }} - {% if object %}{{ object }}{% else %}{% trans "API Clients" %}{% endif %}{% endblock %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% trans "API Clients" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'a2-manager-api-clients' %}">{% trans "API Clients" %}</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "authentic2/manager/api_client_common.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'a2-manager-api-clients' %}">{% trans "API clients" %}</a>
|
||||
<a href="#"></a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Do you want to delete "{{ object }}" ?{% endblocktrans %}</p>
|
||||
<div class="buttons">
|
||||
<button class="delete-button">{% trans "Delete" %}</button>
|
||||
<a class="cancel" href="{% url 'a2-manager-api-client-detail' pk=object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "authentic2/manager/api_client_common.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="#"></a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{{ block.super }}
|
||||
<span class="actions">
|
||||
<a href="{% url 'a2-manager-api-client-delete' pk=api_client.pk %}" rel="popup">{% trans "Delete" %}</a>
|
||||
<a href="{% url 'a2-manager-api-client-edit' pk=api_client.pk %}">{% trans "Edit" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if api_client.description %}
|
||||
<div class="bo-block">{{api_client.description}}</div>
|
||||
{% endif %}
|
||||
<div class="bo-block">
|
||||
<h3>{% trans "Parameters" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "identifier" %} : {{api_client.identifier}}</li>
|
||||
<li>{% trans "password" %} : {{api_client.password}}</li>
|
||||
{% if api_client.restrict_to_anonymised_data %}<li>{% trans "Restricted to anonymised data" %}</li>{% endif %}
|
||||
{% if api_client.apiclient_roles.count %}
|
||||
<li>{% trans "Roles:" %}
|
||||
<ul>
|
||||
{% for role in api_client.apiclient_roles.all %}<li>{{ role.name }}</li>{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "authentic2/manager/api_client_common.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="#"></a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Submit" %}</button>
|
||||
<a class="cancel" href="{{cancel_url}}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "authentic2/manager/api_client_common.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block appbar %}
|
||||
{{ block.super }}
|
||||
<span class="actions">
|
||||
<a href="{% url 'a2-manager-api-client-add' %}">{% trans "Add new API client" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if object_list %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for api_client in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'a2-manager-api-client-detail' pk=api_client.pk %}">{{api_client.name}} ({{api_client.identifier}})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="infonotice">{% trans "There are no API client defined." %}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -41,6 +41,7 @@
|
|||
{% if display_tech_info %}
|
||||
<a id="tech-info" class="button button-paragraph" href="{% url 'a2-manager-tech-info' %}">{% trans 'Directory servers' %}</a>
|
||||
{% endif %}
|
||||
<a id="api-clients" class="button button-paragraph" href="{% url 'a2-manager-api-clients' %}">{% trans 'API Clients' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -191,6 +191,13 @@ urlpatterns = required(
|
|||
url(r'^site-import/$', views.site_import, name='a2-manager-site-import'),
|
||||
# technical information including ldap config
|
||||
url(r'^tech-info/$', views.tech_info, name='a2-manager-tech-info'),
|
||||
url(r'^api-clients/$', views.api_clients, name='a2-manager-api-clients'),
|
||||
url(r'^api-clients/add/$', views.api_client_add, name='a2-manager-api-client-add'),
|
||||
url(r'^api-clients/(?P<pk>\d+)/$', views.api_client_detail, name='a2-manager-api-client-detail'),
|
||||
url(r'^api-clients/(?P<pk>\d+)/edit/$', views.api_client_edit, name='a2-manager-api-client-edit'),
|
||||
url(
|
||||
r'^api-clients/(?P<pk>\d+)/delete/$', views.api_client_delete, name='a2-manager-api-client-delete'
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ from django.views.generic import CreateView, DeleteView, DetailView, FormView, T
|
|||
from django.views.generic.base import ContextMixin
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.views.generic.list import ListView
|
||||
from django_select2.views import AutoResponseView
|
||||
from django_tables2 import SingleTableMixin, SingleTableView
|
||||
from gadjo.templatetags.gadjo import xstatic
|
||||
|
@ -45,6 +46,7 @@ from authentic2.backends import ldap_backend
|
|||
from authentic2.data_transfer import ImportContext, export_site, import_site
|
||||
from authentic2.decorators import json as json_view
|
||||
from authentic2.forms.profile import modelform_factory
|
||||
from authentic2.models import APIClient
|
||||
from authentic2.utils import crypto
|
||||
from authentic2.utils.misc import batch_queryset, redirect
|
||||
|
||||
|
@ -843,3 +845,75 @@ class SearchOUMixin:
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(ou=self.ou, **kwargs)
|
||||
|
||||
|
||||
class APIClientsMixin(MediaMixin, TitleMixin):
|
||||
model = APIClient
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.all()
|
||||
|
||||
|
||||
class APIClientsView(APIClientsMixin, ListView):
|
||||
template_name = 'authentic2/manager/api_clients.html'
|
||||
title = _('API clients')
|
||||
|
||||
|
||||
api_clients = APIClientsView.as_view()
|
||||
|
||||
|
||||
class APIClientDetailView(APIClientsMixin, DetailView):
|
||||
|
||||
template_name = 'authentic2/manager/api_client_detail.html'
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return str(self.object)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['api_client'] = self.object
|
||||
return context
|
||||
|
||||
|
||||
api_client_detail = APIClientDetailView.as_view()
|
||||
|
||||
|
||||
class APIClientAddView(APIClientsMixin, CreateView):
|
||||
template_name = 'authentic2/manager/api_client_form.html'
|
||||
title = _('New API client')
|
||||
form_class = forms.APIClientForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse('a2-manager-api-clients')
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('a2-manager-api-client-detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
api_client_add = APIClientAddView.as_view()
|
||||
|
||||
|
||||
class APIClientEditView(APIClientsMixin, UpdateView):
|
||||
template_name = 'authentic2/manager/api_client_form.html'
|
||||
title = _('Edit API client')
|
||||
form_class = forms.APIClientForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse('a2-manager-api-client-detail', kwargs={'pk': self.object.pk})
|
||||
return context
|
||||
|
||||
|
||||
api_client_edit = APIClientEditView.as_view()
|
||||
|
||||
|
||||
class APIClientDeleteView(APIClientsMixin, DeleteView):
|
||||
template_name = 'authentic2/manager/api_client_delete.html'
|
||||
title = _('Delete API client')
|
||||
success_url = reverse_lazy('a2-manager-api-clients')
|
||||
|
||||
|
||||
api_client_delete = APIClientDeleteView.as_view()
|
||||
|
|
|
@ -633,6 +633,11 @@ class APIClient(models.Model):
|
|||
verbose_name = _('APIClient')
|
||||
verbose_name_plural = _('APIClient')
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
return '%s - %s' % (self._meta.verbose_name, self.name)
|
||||
return str(self._meta.verbose_name)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return True
|
||||
|
@ -704,3 +709,6 @@ class APIClient(models.Model):
|
|||
return functools.reduce(operator.__or__, results)
|
||||
else:
|
||||
return qs
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('a2-manager-api-client-detail', kwargs={'pk': self.pk})
|
||||
|
|
|
@ -33,7 +33,7 @@ from authentic2.a2_rbac.models import OrganizationalUnit as OU
|
|||
from authentic2.a2_rbac.models import Permission, Role
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
from authentic2.apps.journal.models import Event
|
||||
from authentic2.models import Service
|
||||
from authentic2.models import APIClient, Service
|
||||
from authentic2.validators import EmailValidator
|
||||
from authentic2_idp_oidc import app_settings as oidc_app_settings
|
||||
from authentic2_idp_oidc.models import OIDCClaim, OIDCClient
|
||||
|
@ -61,8 +61,8 @@ def test_manager_login(superuser_or_admin, app):
|
|||
path = reverse('a2-manager-%s' % section)
|
||||
assert manager_home_page.pyquery.remove_namespaces()('a.button[href=\'%s\']' % path)
|
||||
|
||||
conf_entries = ['authn', 'journal']
|
||||
assert len(manager_home_page.pyquery('div.a2-manager-id-tools_content').children()) == 2
|
||||
conf_entries = ['authn', 'journal', 'api-clients']
|
||||
assert len(manager_home_page.pyquery('div.a2-manager-id-tools_content').children()) == 3
|
||||
for entry in conf_entries:
|
||||
assert manager_home_page.pyquery(f'a#{entry}')
|
||||
|
||||
|
@ -71,7 +71,7 @@ def test_manager_login(superuser_or_admin, app):
|
|||
with mock.patch('authentic2.backends.ldap_backend.LDAPBackend.get_config', return_value=mocked_config):
|
||||
# new tech info link appears
|
||||
manager_home_page = app.get(reverse('a2-manager-homepage'))
|
||||
assert len(manager_home_page.pyquery('div.a2-manager-id-tools_content').children()) == 3
|
||||
assert len(manager_home_page.pyquery('div.a2-manager-id-tools_content').children()) == 4
|
||||
assert manager_home_page.pyquery('a#tech-info')
|
||||
|
||||
|
||||
|
@ -1339,3 +1339,99 @@ def test_manager_menu_json(app, admin):
|
|||
response = login(app, admin)
|
||||
response = app.get('/manage/menu.json')
|
||||
assert response.json == expected
|
||||
|
||||
|
||||
def test_manager_api_client_list_empty(superuser_or_admin, app):
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-clients')
|
||||
assert 'There are no API client defined.' in resp.text
|
||||
|
||||
|
||||
def test_manager_api_client_list_add_button(superuser_or_admin, app):
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-clients')
|
||||
anchor = resp.pyquery('span.actions a[href="%s"]' % reverse('a2-manager-api-client-add'))
|
||||
assert anchor.text() == 'Add new API client'
|
||||
|
||||
|
||||
def test_manager_api_client_list_show_objects(superuser_or_admin, app):
|
||||
api_client = APIClient.objects.create(
|
||||
name='foo', description='foo-description', identifier='foo-description', password='foo-password'
|
||||
)
|
||||
url = '/manage/api-clients/%s/' % api_client.pk
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-clients')
|
||||
anchor = resp.pyquery('div.content ul.objects-list a[href="%s"]' % url)
|
||||
assert anchor.text() == 'foo (foo-description)'
|
||||
|
||||
|
||||
def test_manager_api_client_add(superuser_or_admin, app):
|
||||
assert APIClient.objects.count() == 0
|
||||
role_1 = Role.objects.create(name='role-1')
|
||||
role_2 = Role.objects.create(name='role-2')
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-client-add')
|
||||
form = resp.form
|
||||
form.set('name', 'api-client-name')
|
||||
form.set('description', 'api-client-description')
|
||||
form.set('identifier', 'api-client-identifier')
|
||||
form.set('password', 'api-client-password')
|
||||
form['apiclient_roles'].force_value([role_1.id, role_2.id])
|
||||
response = form.submit().follow()
|
||||
assert APIClient.objects.count() == 1
|
||||
api_client = APIClient.objects.get(name='api-client-name')
|
||||
assert set(api_client.apiclient_roles.all()) == {role_1, role_2}
|
||||
assert urlparse(response.request.url).path == api_client.get_absolute_url()
|
||||
|
||||
|
||||
def test_manager_api_client_detail(superuser_or_admin, app):
|
||||
role_1 = Role.objects.create(name='role-1')
|
||||
role_2 = Role.objects.create(name='role-2')
|
||||
api_client = APIClient.objects.create(
|
||||
name='foo',
|
||||
description='foo-description',
|
||||
identifier='foo-identifier',
|
||||
password='foo-password',
|
||||
restrict_to_anonymised_data=True,
|
||||
)
|
||||
api_client.apiclient_roles.add(role_1, role_2)
|
||||
resp = login(app, superuser_or_admin, api_client.get_absolute_url())
|
||||
assert 'identifier : foo-identifier' in resp.text
|
||||
assert 'password : foo-password' in resp.text
|
||||
assert 'foo-description' in resp.text
|
||||
assert 'Restricted to anonymised data' in resp.text
|
||||
assert 'role-1' in resp.text
|
||||
assert 'role-2' in resp.text
|
||||
|
||||
edit_button = resp.pyquery(
|
||||
'span.actions a[href="%s"]' % reverse('a2-manager-api-client-edit', kwargs={'pk': api_client.pk})
|
||||
)
|
||||
assert edit_button
|
||||
assert edit_button.text() == 'Edit'
|
||||
delete_button = resp.pyquery(
|
||||
'span.actions a[href="%s"]' % reverse('a2-manager-api-client-delete', kwargs={'pk': api_client.pk})
|
||||
)
|
||||
assert delete_button
|
||||
assert delete_button.text() == 'Delete'
|
||||
|
||||
|
||||
def test_manager_api_client_edit(superuser_or_admin, app):
|
||||
api_client = APIClient.objects.create(
|
||||
name='foo', description='foo-description', identifier='foo-identifier', password='foo-password'
|
||||
)
|
||||
assert APIClient.objects.count() == 1
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-client-edit', kwargs={'pk': api_client.pk})
|
||||
form = resp.form
|
||||
resp.form.set('password', 'easy')
|
||||
response = form.submit().follow()
|
||||
assert urlparse(response.request.url).path == api_client.get_absolute_url()
|
||||
assert APIClient.objects.count() == 1
|
||||
api_client = APIClient.objects.get(password='easy')
|
||||
assert api_client.identifier == 'foo-identifier'
|
||||
|
||||
|
||||
def test_manager_api_client_delete(superuser_or_admin, app):
|
||||
api_client = APIClient.objects.create(
|
||||
name='foo', description='foo-description', identifier='foo-identifier', password='foo-password'
|
||||
)
|
||||
assert APIClient.objects.count() == 1
|
||||
resp = login(app, superuser_or_admin, 'a2-manager-api-client-delete', kwargs={'pk': api_client.pk})
|
||||
response = resp.form.submit().follow()
|
||||
assert urlparse(response.request.url).path == reverse('a2-manager-api-clients')
|
||||
assert APIClient.objects.count() == 0
|
||||
|
|
Loading…
Reference in New Issue