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_ldap.py | ||
---|---|---|
26 | 26 |
from django.contrib.auth import get_user_model |
27 | 27 |
from django.core import mail, management |
28 | 28 |
from django.core.exceptions import ImproperlyConfigured |
29 |
from django.urls import reverse |
|
29 | 30 |
from django.utils import timezone |
30 | 31 |
from django.utils.encoding import force_bytes, force_text |
31 | 32 |
from ldap.dn import escape_dn_chars |
... | ... | |
2108 | 2109 |
) |
2109 | 2110 |
user = ldap_backend.LDAPUser.objects.get(username='%s@ldap' % UID) |
2110 | 2111 |
utils.assert_event('user.login.failure', user=user, username=UID, reason=exception[1]) |
2112 | ||
2113 | ||
2114 |
def test_technical_info_ldap(app, admin, superuser, slapd, settings, monkeypatch): |
|
2115 |
settings.LDAP_AUTH_SETTINGS = [ |
|
2116 |
{ |
|
2117 |
'url': [slapd.ldap_url], |
|
2118 |
'binddn': force_text('cn=%s,o=ôrga' % escape_dn_chars('Étienne Michu')), |
|
2119 |
'bindpw': 'passé', |
|
2120 |
'basedn': 'o=ôrga', |
|
2121 |
'use_tls': False, |
|
2122 |
} |
|
2123 |
] |
|
2124 | ||
2125 |
utils.login(app, admin, 'a2-manager-homepage') |
|
2126 |
app.get(reverse('a2-manager-tech-info'), status=403) |
|
2127 |
utils.logout(app) |
|
2128 | ||
2129 |
resp = utils.login(app, superuser, 'a2-manager-tech-info') |
|
2130 |
ldap_config_text = resp.pyquery('div#a2-manager-tech-info-ldap-list').text() |
|
2131 | ||
2132 |
assert 'Base ldapsearch command' in ldap_config_text |
|
2133 |
assert 'ldapsearch -v -H ldapi://' in ldap_config_text |
|
2134 |
assert '-D "cn=Étienne Michu,o=ôrga" -w "passé" -b "o=ôrga" "(|(mail=*)(uid=*))"' in ldap_config_text |
|
2135 | ||
2136 |
for opt in [ |
|
2137 |
'active_directory', |
|
2138 |
'attribute_mappings', |
|
2139 |
'attributes', |
|
2140 |
'basedn', |
|
2141 |
'bind_with_username', |
|
2142 |
'binddn', |
|
2143 |
'bindpw', |
|
2144 |
'bindsasl', |
|
2145 |
'cacertdir', |
|
2146 |
'cacertfile', |
|
2147 |
'can_reset_password', |
|
2148 |
'certfile', |
|
2149 |
'clean_external_id_on_update', |
|
2150 |
'connect_with_user_credentials', |
|
2151 |
'create_group', |
|
2152 |
'disable_update', |
|
2153 |
'email_field', |
|
2154 |
'external_id_tuples', |
|
2155 |
'extra_attributes', |
|
2156 |
'fname_field', |
|
2157 |
'global_ldap_options', |
|
2158 |
'group_basedn', |
|
2159 |
'group_filter', |
|
2160 |
'group_mapping', |
|
2161 |
'group_to_role_mapping', |
|
2162 |
'groupactive', |
|
2163 |
'groupstaff', |
|
2164 |
'groupsu', |
|
2165 |
'is_staff', |
|
2166 |
'is_superuser', |
|
2167 |
'keep_password', |
|
2168 |
'keep_password_in_session', |
|
2169 |
'keyfile', |
|
2170 |
'ldap_options', |
|
2171 |
'ldap_uri', |
|
2172 |
'limit_to_realm', |
|
2173 |
'lname_field', |
|
2174 |
'lookups', |
|
2175 |
'mandatory_attributes_values', |
|
2176 |
'member_of_attribute', |
|
2177 |
'multimatch', |
|
2178 |
'ou_slug', |
|
2179 |
'ppolicy_dn', |
|
2180 |
'realm', |
|
2181 |
'referrals', |
|
2182 |
'replicas', |
|
2183 |
'require_cert', |
|
2184 |
'set_mandatory_groups', |
|
2185 |
'set_mandatory_roles', |
|
2186 |
'shuffle_replicas', |
|
2187 |
'sync_ldap_users_filter', |
|
2188 |
'timeout', |
|
2189 |
'update_username', |
|
2190 |
'url', |
|
2191 |
'use_controls', |
|
2192 |
'use_first_url_for_external_id', |
|
2193 |
'use_password_modify', |
|
2194 |
'use_tls', |
|
2195 |
'user_attributes', |
|
2196 |
'user_basedn', |
|
2197 |
'user_can_change_password', |
|
2198 |
'user_dn_template', |
|
2199 |
'user_filter', |
|
2200 |
'username_template', |
|
2201 |
]: |
|
2202 |
assert opt in ldap_config_text |
|
2203 | ||
2204 |
# mock a buggy connection |
|
2205 |
monkeypatch.setattr(ldap_backend.LDAPBackend, 'get_connection', lambda x: None) |
|
2206 |
resp = app.get(reverse('a2-manager-tech-info')) |
|
2207 |
ldap_config_text = resp.pyquery('div#a2-manager-tech-info-ldap-list').text() |
|
2208 | ||
2209 |
assert 'Base ldapsearch command' not in ldap_config_text |
|
2210 |
assert 'Error while attempting to connect to LDAP server' in ldap_config_text |
|
2111 |
- |