0001-manager-add-a-technical-info-page-with-ldap-configs-.patch
src/authentic2/manager/static/authentic2/manager/css/style.css | ||
---|---|---|
277 | 277 |
a.role-inheritance-view-all { |
278 | 278 |
font-style: italic; |
279 | 279 |
} |
280 | ||
281 |
div.a2-manager-ldap pre { |
|
282 |
background: white; |
|
283 |
border: 1px solid black; |
|
284 |
padding: .3em; |
|
285 |
overflow-x: auto; |
|
286 |
white-space: pre-wrap; |
|
287 |
word-wrap: break-word; |
|
288 |
} |
src/authentic2/manager/templates/authentic2/manager/homepage.html | ||
---|---|---|
17 | 17 |
{% if user.is_superuser or can_view_journal %} |
18 | 18 |
<li><a href="{% url 'a2-manager-journal' %}">{% trans 'Journal' %}</a></li> |
19 | 19 |
{% endif %} |
20 |
{% if user.is_superuser %} |
|
21 |
<li><a href="{% url 'a2-manager-tech-info' %}">{% trans 'Technical information' %}</a></li> |
|
22 |
{% endif %} |
|
20 | 23 |
</ul> |
21 | 24 |
</span> |
22 | 25 |
{% endif %} |
src/authentic2/manager/templates/authentic2/manager/ldap_details.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 |
<h4>{% trans "Realm:" %} {{ ldap.realm }}</h4> |
|
3 |
<div class="a2-manager-ldap-{{ ldap.realm }}"> |
|
4 |
{% if not error %} |
|
5 |
<h5>{% blocktrans %}Base ldapsearch command:{% endblocktrans %}</h5> |
|
6 |
<pre>ldapsearch -v -H {{ ldap.ldap_uri }} -D "{{ ldap.binddn }}" -w "{{ ldap.bindpw }}" -b "{{ ldap.basedn }}"{% if ldap.user_filter or ldap.sync_ldap_users_filter %} "{% firstof ldap.sync_ldap_users_filter ldap.user_filter %}"{% endif %}</pre> |
|
7 |
{% else %} |
|
8 |
<div class="error"> |
|
9 |
{% blocktrans %}Error while attempting to connect to LDAP server, base ldapsearch command won't be displayed.{% endblocktrans %} |
|
10 |
</div> |
|
11 |
{% endif %} |
|
12 |
<h5>{% trans "Configuration:" %}</h5> |
|
13 |
<pre>{{ ldap|pprint }}</pre> |
|
14 |
</div> |
src/authentic2/manager/templates/authentic2/manager/tech_info.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar %} |
|
5 |
<h2>{% blocktrans %}{{ title }}{% endblocktrans %}</h2> |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
{% if ldap_list %} |
|
10 |
<h3>{% trans "LDAP information" %}</h3> |
|
11 |
<div id="a2-manager-tech-info-ldap-list"> |
|
12 |
{% for ldap in ldap_list %} |
|
13 |
{% include "authentic2/manager/ldap_details.html" with ldap=ldap %} |
|
14 |
{% endfor %} |
|
15 |
</div> |
|
16 |
{% endif %} |
|
17 |
{% endblock %} |
src/authentic2/manager/urls.py | ||
---|---|---|
181 | 181 |
# general management |
182 | 182 |
url(r'^site-export/$', views.site_export, name='a2-manager-site-export'), |
183 | 183 |
url(r'^site-import/$', views.site_import, name='a2-manager-site-import'), |
184 |
# technical information including ldap config |
|
185 |
url(r'^tech-info/$', views.tech_info, name='a2-manager-tech-info'), |
|
184 | 186 |
], |
185 | 187 |
) |
186 | 188 |
src/authentic2/manager/views.py | ||
---|---|---|
661 | 661 |
homepage = HomepageView.as_view() |
662 | 662 | |
663 | 663 | |
664 |
class TechnicalInformationView(TitleMixin, MediaMixin, TemplateView): |
|
665 |
template_name = 'authentic2/manager/tech_info.html' |
|
666 |
title = _("Technical information") |
|
667 | ||
668 |
def get(self, request, *args, **kwargs): |
|
669 |
if not request.user.is_superuser: |
|
670 |
raise PermissionDenied |
|
671 |
return super().get(request, *args, **kwargs) |
|
672 | ||
673 |
def get_context_data(self, **kwargs): |
|
674 |
import ldap |
|
675 | ||
676 |
from authentic2.backends import ldap_backend |
|
677 | ||
678 |
backend = ldap_backend.LDAPBackend |
|
679 |
kwargs['ldap_list'] = [] |
|
680 |
for block in backend.get_config(): |
|
681 |
config = block.copy() |
|
682 |
conn = backend.get_connection(config) |
|
683 |
if not conn: |
|
684 |
kwargs['error'] = True |
|
685 |
else: |
|
686 |
# retrieve ldap uri, not directly visible in configuration block |
|
687 |
config['ldap_uri'] = conn.get_option(ldap.OPT_URI) |
|
688 |
# user filters need to be formatted to ldapsearch syntax |
|
689 |
config['user_filter'] = force_text(block.get('user_filter'), '').replace('%s', '*') |
|
690 |
config['sync_ldap_users_filter'] = ( |
|
691 |
force_text(block.get('sync_ldap_users_filter'), '').replace('%s', '*').replace('%s', '*') |
|
692 |
) |
|
693 | ||
694 |
kwargs['ldap_list'].append(config) |
|
695 |
return super().get_context_data(**kwargs) |
|
696 | ||
697 | ||
698 |
tech_info = TechnicalInformationView.as_view() |
|
699 | ||
700 | ||
664 | 701 |
class MenuJson(HomepageView): |
665 | 702 |
def get(self, request, *args, **kwargs): |
666 | 703 |
menu_entries = [] |
tests/test_manager.py | ||
---|---|---|
24 | 24 |
from django.core import mail |
25 | 25 |
from django.test.utils import override_settings |
26 | 26 |
from django.urls import reverse |
27 |
from django.utils.encoding import force_bytes, force_str |
|
27 |
from django.utils.encoding import force_bytes, force_str, force_text
|
|
28 | 28 |
from webtest import Upload |
29 | 29 | |
30 | 30 |
from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP |
... | ... | |
37 | 37 |
from django_rbac.models import VIEW_OP |
38 | 38 |
from django_rbac.utils import get_operation |
39 | 39 | |
40 |
from .utils import assert_event, get_link_from_mail, login, request_select2 |
|
40 |
from .test_ldap import slapd |
|
41 |
from .utils import assert_event, get_link_from_mail, login, logout, request_select2 |
|
41 | 42 | |
42 | 43 |
pytestmark = pytest.mark.django_db |
43 | 44 | |
... | ... | |
1197 | 1198 |
resp = resp.form.submit() |
1198 | 1199 |
assert 'Test Service' in resp.text |
1199 | 1200 |
assert 'Example Service' not in resp.text |
1201 | ||
1202 | ||
1203 |
def test_technical_info_ldap(app, admin, superuser, slapd, settings, monkeypatch): |
|
1204 |
from ldap.dn import escape_dn_chars |
|
1205 | ||
1206 |
from authentic2.backends import ldap_backend |
|
1207 | ||
1208 |
settings.LDAP_AUTH_SETTINGS = [ |
|
1209 |
{ |
|
1210 |
'url': [slapd.ldap_url], |
|
1211 |
'binddn': force_text('cn=%s,o=ôrga' % escape_dn_chars('Étienne Michu')), |
|
1212 |
'bindpw': 'passé', |
|
1213 |
'basedn': 'o=ôrga', |
|
1214 |
'use_tls': False, |
|
1215 |
} |
|
1216 |
] |
|
1217 |
assert slapd |
|
1218 | ||
1219 |
login(app, admin, 'a2-manager-homepage') |
|
1220 |
app.get(reverse('a2-manager-tech-info'), status=403) |
|
1221 |
logout(app) |
|
1222 | ||
1223 |
resp = login(app, superuser, 'a2-manager-tech-info') |
|
1224 |
ldap_config_text = resp.pyquery('div#a2-manager-tech-info-ldap-list').text() |
|
1225 | ||
1226 |
assert 'Base ldapsearch command' in ldap_config_text |
|
1227 |
assert 'ldapsearch -v -H ldapi://' in ldap_config_text |
|
1228 |
assert '-D "cn=Étienne Michu,o=ôrga" -w "passé" -b "o=ôrga" "(|(mail=*)(uid=*))"' in ldap_config_text |
|
1229 | ||
1230 |
for opt in [ |
|
1231 |
'active_directory', |
|
1232 |
'attribute_mappings', |
|
1233 |
'attributes', |
|
1234 |
'basedn', |
|
1235 |
'bind_with_username', |
|
1236 |
'binddn', |
|
1237 |
'bindpw', |
|
1238 |
'bindsasl', |
|
1239 |
'cacertdir', |
|
1240 |
'cacertfile', |
|
1241 |
'can_reset_password', |
|
1242 |
'certfile', |
|
1243 |
'clean_external_id_on_update', |
|
1244 |
'connect_with_user_credentials', |
|
1245 |
'create_group', |
|
1246 |
'disable_update', |
|
1247 |
'email_field', |
|
1248 |
'external_id_tuples', |
|
1249 |
'extra_attributes', |
|
1250 |
'fname_field', |
|
1251 |
'global_ldap_options', |
|
1252 |
'group_basedn', |
|
1253 |
'group_filter', |
|
1254 |
'group_mapping', |
|
1255 |
'group_to_role_mapping', |
|
1256 |
'groupactive', |
|
1257 |
'groupstaff', |
|
1258 |
'groupsu', |
|
1259 |
'is_staff', |
|
1260 |
'is_superuser', |
|
1261 |
'keep_password', |
|
1262 |
'keep_password_in_session', |
|
1263 |
'keyfile', |
|
1264 |
'ldap_options', |
|
1265 |
'ldap_uri', |
|
1266 |
'limit_to_realm', |
|
1267 |
'lname_field', |
|
1268 |
'lookups', |
|
1269 |
'mandatory_attributes_values', |
|
1270 |
'member_of_attribute', |
|
1271 |
'multimatch', |
|
1272 |
'ou_slug', |
|
1273 |
'ppolicy_dn', |
|
1274 |
'realm', |
|
1275 |
'referrals', |
|
1276 |
'replicas', |
|
1277 |
'require_cert', |
|
1278 |
'set_mandatory_groups', |
|
1279 |
'set_mandatory_roles', |
|
1280 |
'shuffle_replicas', |
|
1281 |
'sync_ldap_users_filter', |
|
1282 |
'timeout', |
|
1283 |
'update_username', |
|
1284 |
'url', |
|
1285 |
'use_controls', |
|
1286 |
'use_first_url_for_external_id', |
|
1287 |
'use_password_modify', |
|
1288 |
'use_tls', |
|
1289 |
'user_attributes', |
|
1290 |
'user_basedn', |
|
1291 |
'user_can_change_password', |
|
1292 |
'user_dn_template', |
|
1293 |
'user_filter', |
|
1294 |
'username_template', |
|
1295 |
]: |
|
1296 |
assert opt in ldap_config_text |
|
1297 | ||
1298 |
# mock a buggy connection |
|
1299 |
monkeypatch.setattr(ldap_backend.LDAPBackend, 'get_connection', lambda x: None) |
|
1300 |
resp = app.get(reverse('a2-manager-tech-info')) |
|
1301 |
ldap_config_text = resp.pyquery('div#a2-manager-tech-info-ldap-list').text() |
|
1302 | ||
1303 |
assert 'Base ldapsearch command' not in ldap_config_text |
|
1304 |
assert 'Error while attempting to connect to LDAP server' in ldap_config_text |
|
1200 |
- |